import { attach } from "effector";
Available since effector 20.13.0.
Since effector 22.4.0, it is available to check whether effect is created via attach
method — is.attached.
Creates new effects based on the other effects, stores. Allows mapping params and handling errors.
Use cases: declarative way to pass values from stores to effects and argument preprocessing. Most useful case is attach({ source, async effect })
.
The attached effects are the same first-class citizens as the regular effects made by createEffect. You should place them in the same files as regular effects, also you can use the same naming strategy.
Methods
attach({effect})
Create effect which will call effect
with params as it is. That allows creating separate effects with shared behavior.
Formulae
const attachedFx = attach({ effect: originalFx });
- When
attachedFx
is triggered, thenoriginalFx
is triggered too - When
originalFx
is finished (fail/done), thenattachedFx
must be finished with the same state.
Arguments
effect
(Effect): Wrapped effect
Returns
Effect: New effect
Types
const originalFx: Effect<Params, Done, Fail>;
const attachedFx: Effect<Params, Done, Fail> = attach({
effect: originalFx,
});
In case of this simple variant of attach
, types of originalFx
and attachedFx
will be the same.
Examples
It allows to create local copy of the effect, to react only on triggers emitted from the current local code.
import { createEffect, attach } from "effector";
const originalFx = createEffect((word: string) => {
console.info("Printed:", word);
});
const attachedFx = attach({ effect: originalFx });
originalFx.watch(() => console.log("originalFx"));
originalFx.done.watch(() => console.log("originalFx.done"));
attachedFx.watch(() => console.log("attachedFx"));
attachedFx.done.watch(() => console.log("attachedFx.done"));
originalFx("first");
// => originalFx
// => Printed: first
// => originalFx.done
attachedFx("second");
// => attachedFx
// => originalFx
// Printed: second
// => originalFx.done
// => attachedFx.done
attach({source, effect})
Create effect which will trigger given one with values from source
stores.
Formulae
const attachedFx = attach({
source,
effect: originalFx,
});
- When
attachedFx
is triggered, read data fromsource
, trigger with the dataoriginalFx
- When
originalFx
is finished, pass the same resolution (done/fail) intoattachedFx
and finish it
Arguments
source
(Store |{[key: string]: Store}
): Store or object with stores, values of which will be passed to the second argument ofmapParams
effect
(Effect): Original effect
Returns
Effect: New effect
Types
You don’t need to explicitly set types for each declaration. The purpose of the following example is to provide a clear understanding.
In most userland code you will write code like this, without explicit types of the let
/const
:
const originalFx = createEffect<OriginalParams, SomeResult, SomeError>(async () => {});
const $store = createStore(initialValue);
const attachedFx = attach({
source: $store,
effect: originalFx,
});
Single store
const originalFx: Effect<T, Done, Fail>;
const $store: Store<T>;
const attachedFx: Effect<void, Done, Fail> = attach({
source: $store,
effect: originalFx,
});
Types of the source
store and effect
params must be the same.
But the attachedFx
will omit the type of params, it means the attached effect not requires any params at all.
Shape of stores
const originalFx: Effect<{ a: A; b: B }, Done, Fail>;
const $a: Store<A>;
const $b: Store<B>;
const attachedFx: Effect<void, Done, Fail> = attach({
source: { a: $a, b: $b },
effect: originalFx,
});
Types of the source
object must be the same as originalFx
params. But the attachedFx
will omit the type of params, it means the attached effect not requires any params at all.
Examples
import { createEffect, createStore, attach } from "effector";
const requestPageFx = createEffect<{ page: number; size: number }, string[]>(
async ({ page, size }) => {
console.log("Requested", page);
return page * size;
},
);
const $page = createStore(1);
const $size = createStore(20);
const requestNextPageFx = attach({
source: { page: $page, size: $size },
effect: requestPageFx,
});
$page.on(requestNextPageFx.done, (page) => page + 1);
requestPageFx.doneData.watch((position) => console.log("requestPageFx.doneData", position));
await requestNextPageFx();
// => Requested 1
// => requestPageFx.doneData 20
await requestNextPageFx();
// => Requested 2
// => requestPageFx.doneData 40
await requestNextPageFx();
// => Requested 3
// => requestPageFx.doneData 60
attach({source, async effect})
Creates effect which will call async function with values from the source
stores.
Formulae
const attachedFx = attach({
source,
async effect(source, params) {},
});
- When
attachedFx
is triggered, read data from thesource
, calleffect
function. - When
effect
function returns resolvedPromise
, finishattachedFx
with the data from the function asattachedFx.done
. - When
effect
throws exception, or returns rejectedPromise
, finishattachedFx
with the data from function asattachedFx.fail
.
Arguments
effect
(Function):(source: Source, params: Params) => Promise<Result> | Result
source
(Store |{[key: string]: Store}
): Store or object with stores, values of which will be passed to the first argument ofeffect
Returns
Effect: New effect
Usage with scope
Any effects called inside async effect
function will propagate scope.
const outerFx = createEffect((count: number) => {
console.log("Hit", count);
});
const $store = createStore(0);
const attachedFx = attach({
source: $store,
async effect(count, _: void) {},
});
Scope is lost if there are any asynchronous function calls made:
const attachedFx = attach({
source: $store,
async effect(source) {
// Here is ok, the effect is called
const resultA = await anotherFx();
// Be careful:
const resultB = await regularFunction();
// Here scope is lost.
},
});
To solve this case, you need to just wrap your regularFunction
into effect:
const regularFunctionFx = createEffect(regularFunction);
Types
Single store
const $store: Store<T>;
const attachedFx: Effect<Params, Done, Fail> = attach({
source: $store,
async effect(source, params: Params): Done | Promise<Done> {},
});
You need to type explicitly only params
argument. All other types of arguments should be inferred automatically. Also, you may want to explicitly set the return type of the effect
function.
If you want to remove any arguments from the attachedFx
you need to just remove second argument from effect
function:
const attachedFx: Effect<void, void, Fail> = attach({
source: $store,
async effect(source) {},
});
Multiple stores
For details review previous section of types. Here the same logic.
// Userland example, without explicit type declarations
const $foo = createStore(100);
const $bar = createStore("demo");
const attachedFx = attach({
source: { foo: $foo, bar: $bar },
async effect({ foo, bar }, { baz }: { baz: boolean }) {
console.log("Hit!", { foo, bar, baz });
},
});
attachedFx({ baz: true });
// => Hit! { foo: 100, bar: "demo", baz: true }
Example
Please, open pull request via “Edit this page” link.
attach({effect, mapParams})
Creates effect which will trigger given one by transforming params by mapParams
function.
Formulae
const attachedFx = attach({
effect: originalFx,
mapParams,
});
- When
attachedFx
triggered, payload passed intomapParams
function, then the result of it passed intooriginalFx
- When
originalFx
is finished, thenattachedFx
must be finished with the same resolution (done/fail). - If
mapParams
throws an exception, thenattachedFx
must be finished with the error asattachedFx.fail
. ButoriginalFx
will not be triggered at all.
Arguments
effect
(Effect): Wrapped effectmapParams
((newParams) => effectParams
): Function which receives new params and maps them to the params of the wrappedeffect
. Works mostly like event.prepend. Errors happened inmapParams
function will force attached effect to fail.
Returns
Effect: New effect
Types
const originalFx: Effect<A, Done, Fail>;
const attachedFx: Effect<B, Done, Fail> = attach({
effect: originalFx,
mapParams: (params: B): A {},
});
mapParams
must return the same type originalFx
receives as params.
If attachedFx
must be called without any arguments, then params
can be safely removed from the mapParams
:
const attachedFx: Effect<void, Done, Fail> = attach({
effect: originalFx,
mapParams: (): A {},
});
But if mapParams
function throws an exception, it is on your own to check types compatibility, because of TypeScript.
const attachedFx: Effect<void, Done, Fail> = attach({
effect: originalFx,
mapParams: (): A {
throw new AnyNonFailType(); // It can be noncompatible with `Fail` type
},
});
Examples
Map arguments
import { createEffect, attach } from "effector";
const originalFx = createEffect((a: { input: number }) => a);
const attachedFx = attach({
effect: originalFx,
mapParams(a: number) {
return { input: a * 100 };
},
});
originalFx.watch((params) => console.log("originalFx started", params));
attachedFx(1);
// => originalFx { input: 100 }
Handle exceptions
import { createEffect, attach } from "effector";
const originalFx = createEffect((a: { a: number }) => a);
const attachedFx = attach({
effect: originalFx,
mapParams(a: number) {
throw new Error("custom error");
return { a };
},
});
attachedFx.failData.watch((error) => console.log("attachedFx.failData", error));
attachedFx(1);
// => attachedFx.failData
// => Error: custom error
attach({source, mapParams, effect})
Creates effect which will read values from source
stores, pass them with params to mapParams
function and then call effect
with the result.
Formulae
This variant of attach
mostly works like the attach({effect, mapParams}). The same things are omitted from this section.
const attachedFx = attach({
source,
mapParams,
effect: originalFx,
});
- When
attachedFx
triggered, payload passed intomapParams
function with value fromsource
store, then the result of it passed intooriginalFx
- When
originalFx
is finished, thenattachedFx
must be finished with the same resolution (done/fail). - If
mapParams
throws an exception, thenattachedFx
must be finished with the error asattachedFx.fail
. ButoriginalFx
will not be triggered at all.
Arguments
source
(Store |{[key: string]: Store}
): Store or object with stores, values of which will be passed to the second argument ofmapParams
mapParams
((newParams, values) => effectParams
): Function which receives new params and current value ofsource
and combines them to the params of the wrappedeffect
. Errors happened inmapParams
function will force attached effect to faileffect
(Effect): Wrapped effect
Returns
Effect: New effect
Types
Please, open pull request via “Edit this page” link.
Examples
With factory
// ./api/request.ts
import { createEffect, createStore } from "effector";
export const backendRequestFx = createEffect(async ({ token, data, resource }) => {
return fetch(`https://example.com/api${resource}`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(data),
});
});
export const $requestsSent = createStore(0);
$requestsSent.on(backendRequestFx, (total) => total + 1);
// ./api/authorized.ts
import { attach, createStore } from "effector";
const $token = createStore("guest_token");
export const authorizedRequestFx = attach({
effect: backendRequestFx,
source: $token,
mapParams: ({ data, resource }, token) => ({ data, resource, token }),
});
export function createRequest(resource) {
return attach({
effect: authorizedRequestFx,
mapParams: (data) => ({ data, resource }),
});
}
// ./api/index.ts
import { createRequest } from "./authorized";
import { $requestsSent } from "./request";
const getUserFx = createRequest("/user");
const getPostsFx = createRequest("/posts");
$requestsSent.watch((total) => {
console.log(`client analytics: sent ${total} requests`);
});
const user = await getUserFx({ name: "alice" });
/*
POST https://example.com/api/user
{"name": "alice"}
Authorization: Bearer guest_token
*/
// => client analytics: sent 1 requests
const posts = await getPostsFx({ user: user.id });
/*
POST https://example.com/api/posts
{"user": 18329}
Authorization: Bearer guest_token
*/
// => client analytics: sent 2 requests
To allow factory works correct, add a path to a ./api/authorized
into factories
option for Babel plugin:
// .babelrc
{
plugins: [
[
"effector/babel-plugin",
{
factories: ["src/path-to-your-entity/api/authorized"],
},
],
],
}
Parameters
attach()
also receives extra parameters, you can use it when you need.
name
attach({ name: string });
It allows us to explicitly set the name of the created attached effect:
import { attach } from "effector";
const attachedFx = attach({
name: "anotherUsefulName",
source: $store,
async effect(source, params: Type) {
// ...
},
});
attachedFx.shortName; // "anotherUsefulName"
This parameter exists in any variant of the attach
.
domain
attach({ domain: Domain });
It allows to create effect inside specified domain.
Note: this property can only be used with a plain function
effect
.
import { createDomain, createStore, attach } from "effector";
const reportErrors = createDomain();
const $counter = createStore(0);
const attachedFx = attach({
domain: reportErrors,
source: $counter,
async effect(counter) {
// ...
},
});