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
attachedFxis triggered, thenoriginalFxis triggered too - When
originalFxis finished (fail/done), thenattachedFxmust 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.doneattach({source, effect})
Create effect which will trigger given one with values from source stores.
Formulae
const attachedFx = attach({ source, effect: originalFx,});- When
attachedFxis triggered, read data fromsource, trigger with the dataoriginalFx - When
originalFxis finished, pass the same resolution (done/fail) intoattachedFxand finish it
Arguments
source(Store |{[key: string]: Store}): Store or object with stores, values of which will be passed to the second argument ofmapParamseffect(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 60attach({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
attachedFxis triggered, read data from thesource, calleffectfunction. - When
effectfunction returns resolvedPromise, finishattachedFxwith the data from the function asattachedFx.done. - When
effectthrows exception, or returns rejectedPromise, finishattachedFxwith the data from function asattachedFx.fail.
Arguments
effect(Function):(source: Source, params: Params) => Promise<Result> | Resultsource(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 declarationsconst $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
attachedFxtriggered, payload passed intomapParamsfunction, then the result of it passed intooriginalFx - When
originalFxis finished, thenattachedFxmust be finished with the same resolution (done/fail). - If
mapParamsthrows an exception, thenattachedFxmust be finished with the error asattachedFx.fail. ButoriginalFxwill 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 inmapParamsfunction 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 errorattach({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
attachedFxtriggered, payload passed intomapParamsfunction with value fromsourcestore, then the result of it passed intooriginalFx - When
originalFxis finished, thenattachedFxmust be finished with the same resolution (done/fail). - If
mapParamsthrows an exception, thenattachedFxmust be finished with the error asattachedFx.fail. ButoriginalFxwill 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 ofmapParamsmapParams((newParams, values) => effectParams): Function which receives new params and current value ofsourceand combines them to the params of the wrappedeffect. Errors happened inmapParamsfunction 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
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);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 }), });}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 requestsTo allow factory works correct, add a path to a ./api/authorized into factories option for Babel plugin:
{ 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) { // ... },});