Effector.dev Documentation --- # FAQ ## FAQ ### Why do we need babel/swc plugin for SSR? Effector plugins inserts special tags - SIDs - into the code, it help to automate serialization and deserialization of stores, so users doesn't have to think about it. See article about sids for more info. ### Why do we need to give names to events, effects etc. ? This will help in the future, in the development of the effector devtools, and now it is used in the [playground](https://share.effector.dev) on the left sidebar. If you don't want to do it, you can use the [babel plugin](https://www.npmjs.com/package/@effector/babel-plugin). It will automatically generate the name for events and effects from the variable name. # Isolated scopes import Tabs from "@components/Tabs/Tabs.astro"; import TabItem from "@components/Tabs/TabItem.astro"; import SideBySide from "@components/SideBySide/SideBySide.astro"; ## Isolated scopes With scopes you can work with isolated instance for the entire application, which contains an independent clone of all units (including connections between them) and basic methods to access them: ```ts "fork" "allSettled" import { fork, allSettled, createStore, createEvent } from "effector"; // create a new scope const scope = fork(); const $counter = createStore(0); const increment = createEvent(); $counter.on(increment, (state) => state + 1); // trigger the event and wait for the entire chain to complete await allSettled(increment, { scope }); console.log(scope.getState($counter)); // 1 console.log($counter.getState()); // 0 - the original store remains unchanged ``` Using fork, we create a new scope, and with allSettled we run a chain of events and effects inside the specified scope and wait for it to complete. > INFO Scope Independence: > > There is no mechanism for sharing data between scopes; each instance is fully isolated and operates independently. ### Why do we need a scope? In effector, all state is stored globally. In a client-side application (SPA), this is not a problem: each user gets their own instance of the code and works with their own state. But with server-side rendering (SSR) or parallel testing, global state becomes a problem: data from one request or test can “leak” into another. That’s why we need a scope. * **SSR** — the server runs as a single process and serves requests from many users. For each request, you can create a scope that isolates data from effector’s global scope and prevents one user’s state from leaking into another user’s request. * **Testing** — when running tests in parallel, data races and state collisions may occur. A scope allows each test to run with its own isolated state. We provide detailed guides on working with server-side rendering (SSR) and testing. Here, we’ll focus on the core principles of using scopes, their rules, and how to avoid common mistakes. ### Rules for working with scopes To ensure scopes work correctly, there are a few rules to prevent scope loss: #### Effect and promise calls For effect handlers that call other effects, ensure to only call effects, not common asynchronous functions. Furthermore, effect calls should be awaited. Imperative calls of effects are safe because effector remembers the scope in which the imperative call of the effect began and restores it after the call, allowing for another call in sequence. You can call methods like `Promise.all([fx1(), fx2()])` and others from the standard JavaScript API because in these cases, the calls to effects still happen synchronously, and the scope is safely preserved. ```ts wrap data-border="good" data-height="full" // ✅ correct usage for an effect without inner effects const delayFx = createEffect(async () => { await new Promise((resolve) => setTimeout(resolve, 80)); }); // ✅ correct usage for an effect with inner effects const authFx = createEffect(async () => { await loginFx(); await Promise.all([loadProfileFx(), loadSettingsFx()]); }); ``` ```ts wrap data-border="bad" data-height="full" // ❌ incorrect usage for an effect with inner effects const sendWithAuthFx = createEffect(async () => { await authUserFx(); // incorrect! This should be wrapped in an effect. await new Promise((resolve) => setTimeout(resolve, 80)); // scope is lost here. await sendMessageFx(); }); ``` > INFO get attached: > > For scenarios where an effect might call another effect or perform asynchronous computations, but not both, consider utilizing the attach method instead for more succinct imperative calls. #### Using units with frameworks Always use the `useUnit` hook with frameworks so effector can invoke the unit in the correct scope: ```tsx wrap "useUnit" import { useUnit } from "effector-react"; import { $counter, increased, sendToServerFx } from "./model"; const Component = () => { const [counter, increase, sendToServer] = useUnit([$counter, increased, sendToServerFx]); return (
); }; ``` Alright, just show me how it works already. ### Using in SSR Imagine a website with SSR, where the profile page shows a list of the user’s personal notifications. If we don’t use a scope, here’s what happens: * User A makes a request → their notifications load into `$notifications` on the server. * Almost at the same time, User B makes a request → the store is overwritten with their data. * As a result, both users see User B’s notifications. Not what we want, right? This is a [race condition](https://en.wikipedia.org/wiki/Race_condition), which leads to a leak of private data. With a scope, we get an isolated context that only works for the current user: A request is made → a scope is created → we update state only inside this scope. This works for each request. ```tsx "fork" "allSettled" "serialize" // server.tsx import { renderToString } from "react-dom/server"; import { fork, serialize, allSettled } from "effector"; import { Provider } from "effector-react"; import { fetchNotificationsFx } from "./model"; async function serverRender() { const scope = fork(); // Load data on the server await allSettled(fetchNotificationsFx, { scope }); // Render the app const html = renderToString( , ); // Serialize state to send to the client const data = serialize(scope); return `
${html}
`; } ```
```tsx // client.tsx import { hydrateRoot } from "react-dom/client"; import { fork } from "effector"; // hydrate scope with initial values const scope = fork({ values: window.INITIAL_DATA, }); hydrateRoot( document.getElementById("root"), , ); ```
Things to note in this example: 1. We serialized data using serialize to correctly transfer it to the client. 2. On the client, we hydrated the stores using the . ### Related APIs and Articles * **API** * Scope – Description of scope and its methods * scopeBind – Method for binding a unit to a scope * fork – Operator for creating a scope * allSettled – Method for running a unit in a given scope and waiting for the entire chain of effects to complete * serialize – Method for obtaining serialized store values * hydrate – Method for hydrating serialized data * **Articles** * What is scope loss and how to fix it * SSR guide * Testing guide * The importance of SIDs for store hydration # Effector React Gate *Gate* is a hook for conditional rendering, based on the current value (or values) in props. It can solve problems such as compiling all required data when a component mounts, or showing an alternative component if there is insufficient data in props. Gate is also useful for routing or animations, similar to ReactTransitionGroup. This enables the creation of a feedback loop by sending props back to a *Store*. Gate can be integrated via the useGate hook or as a component with props. Gate stores and events function as standard units within an application. Gate has two potential states: * **Opened**, indicating the component is mounted. * **Closed**, indicating the component is unmounted.
**Example of using Gate as a component:** ```tsx ``` ## Properties ### `.state` Store > WARNING Important: > > Do not modify the `state` value! It is a derived store and should remain in a predictable state. `Store`: DerivedStore containing the current state of the gate. This state derives from the second argument of useGate and from props when rendering the gate as a component. #### Example ```tsx import { createGate, useGate } from "effector-react"; const Gate = createGate(); Gate.state.watch((state) => console.info("gate state updated", state)); function App() { useGate(Gate, { props: "yep" }); return
Example
; } ReactDOM.render(, root); // => gate state updated { props: "yep" } ``` ### `.open` Event > INFO Important: > > Do not manually invoke this event. It is an event that is triggered based on the gate's state. Event\: Event fired upon gate mounting. ### `.close` Event > INFO Important: > > Do not manually invoke this event. It is an event that is triggered based on the gate's state. Event\: Event fired upon gate unmounting. ### `.status` Store > WARNING Important: > > Do not modify the `status` value! It is a derived store and should remain in a predictable state. Store\: Boolean DerivedStore indicating whether the gate is mounted. #### Example ```tsx import { createGate, useGate } from "effector-react"; const Gate = createGate(); Gate.status.watch((opened) => console.info("is Gate opened?", opened)); // => is Gate opened? false function App() { useGate(Gate); return
Example
; } ReactDOM.render(, root); // => is Gate opened? true ``` # Provider React `Context.Provider` component, which takes any Scope in its `value` prop and makes all hooks in the subtree work with this scope: * `useUnit($store)` (and etc.) will read the state and subscribe to updates of the `$store` in this scope * `useUnit(event)` (and etc.) will bind provided event or effect to this scope ## Usage ### Example Usage Here is an example of `` usage. ```tsx import { createEvent, createStore, fork } from "effector"; import { useUnit, Provider } from "effector-react"; import { render } from "react-dom"; const buttonClicked = createEvent(); const $count = createStore(0); $count.on(buttonClicked, (counter) => counter + 1); const App = () => { const [count, handleClick] = useUnit([$count, buttonClicked]); return ( <>

Count: {count}

); }; const myScope = fork({ values: [[$count, 42]], }); render( , document.getElementById("root"), ); ``` The `` component is placed in the subtree of ``, so its `useUnit([$count, inc])` call will return * State of the `$count` store in the `myScope` * Version of `buttonClicked` event, which is bound to the `myScope`, which, if called, updates the `$count` state in the `myScope` ### Multiple Providers Usage There can be as many `` instances in the tree, as you may need. ```tsx import { fork } from "effector"; import { Provider } from "effector-react"; import { App } from "@/app"; const scopeA = fork(); const scopeB = fork(); const ParallelWidgets = () => ( <> ); ``` ## Provider Properties ### `value` `Scope`: any Scope. All hooks in the subtree will work with this scope. # connect ```ts import { connect } from "effector-react"; ``` > WARNING Deprecated: > > since [effector-react 23.0.0](https://changelog.effector.dev/#effector-react-23-0-0). > > Consider using hooks api in modern projects. Wrapper for useUnit to use during migration from redux and class-based projects. Will merge store value fields to component props. ## Methods ### `connect($store)(Component)` #### Formulae ```ts connect($store: Store)(Component): Component ``` #### Arguments 1. `$store` (Store): store or object with stores #### Returns `(Component) => Component`: Function, which accepts react component and return component with store fields merged into props ### `connect(Component)($store)` #### Formulae ```ts connect(Component)($store: Store): Component ``` #### Arguments 1. `Component` (React.ComponentType): react component #### Returns `($store: Store) => Component`: Function, which accepts a store and returns component with store fields merged into props # createComponent ```ts import { createComponent } from "effector-react"; ``` > WARNING Deprecated: > > since [effector-react 23.0.0](https://changelog.effector.dev/#effector-react-23-0-0). > > You can use hooks api in `createComponent` since [effector-react@20.3.0](https://changelog.effector.dev/#effector-20-3-0). ## Methods ### `createComponent($store, render)` Creates a store-based React component. The `createComponent` method is useful for transferring logic and data of state to your View component. #### Arguments 1. `$store` (*Store | Object*): `Store` or object of `Store` 2. `render` (*Function*): Render function which will be called with props and state #### Returns (*`React.Component`*): Returns a React component. #### Example ```jsx import { createStore, createEvent } from "effector"; import { createComponent } from "effector-react"; const increment = createEvent(); const $counter = createStore(0).on(increment, (n) => n + 1); const MyCounter = createComponent($counter, (props, state) => (
Counter: {state}
)); const MyOwnComponent = () => { // any stuff here return ; }; ``` Try it # createGate ```ts import { createGate, type Gate } from "effector-react"; ``` ## Methods ### `createGate(name?)` Creates a #### Formulae ```ts createGate(name?: string): Gate ``` #### Arguments 1. `name?` (*string*): Optional name which will be used as the name of a created React component #### Returns Gate\ #### Examples ##### Basic Usage ```jsx import React from "react"; import ReactDOM from "react-dom"; import { createGate } from "effector-react"; const Gate = createGate("gate with props"); const App = () => (
); Gate.state.watch((state) => { console.log("current state", state); }); // => current state {} ReactDOM.render(, document.getElementById("root")); // => current state {foo: 'bar'} ReactDOM.unmountComponentAtNode(document.getElementById("root")); // => current state {} ``` Try it ### `createGate(config?)` Creates a , if `defaultState` is defined, Gate.state will be created with passed value. #### Formulae ```ts createGate({ defaultState?: T, domain?: Domain, name?: string }): Gate ``` #### Arguments `config` (*Object*): Optional configuration object * `defaultState?`: Optional default state for Gate.state * `domain?` (): Optional domain which will be used to create gate units (Gate.open event, Gate.state store, and so on) * `name?` (*string*): Optional name which will be used as the name of a created React component #### Returns Gate\ # createStoreConsumer ```ts import { createStoreConsumer } from "effector-react"; ``` > WARNING Deprecated: > > since [effector-react 23.0.0](https://changelog.effector.dev/#effector-react-23-0-0). > > Consider using hooks api in modern projects. ## Methods ### `createStoreConsumer($store)` Creates a store-based React component which is watching for changes in the store. Based on *Render Props* technique. #### Arguments 1. `$store` (Store) #### Returns (`React.Component`) #### Examples ```jsx import { createStore } from "effector"; import { createStoreConsumer } from "effector-react"; const $firstName = createStore("Alan"); const FirstName = createStoreConsumer($firstName); const App = () => {(name) =>

{name}

}
; ``` Try it # effector-react ## Hooks Effector bindings for ReactJS. * useUnit(units) * useList(store, renderItem) * useStoreMap({store, key, fn}) * Deprecated hooks: * useStore(store) * useEvent(unit) ### Components * Provider ### Gate API * Gate * createGate() * useGate(GateComponent, props) ### Higher Order Components API * createComponent(store, render) * createStoreConsumer(store) renders props style * connect(store)(Component) "connect" style ### Import map Package `effector-react` provides couple different entry points for different purposes: * effector-react/compat * effector-react/scope # effector-react/compat ```ts import {} from "effector-react/compat"; ``` The library provides a separate module with compatibility up to IE11 and Chrome 47 (browser for Smart TV devices). > WARNING Bundler, Not Transpiler: > > Since third-party libraries can import `effector-react` directly, you **should not** use transpilers like Babel to replace `effector-react` with `effector-react/compat` in your code because by default, Babel will not transform third-party code. > > **Use a bundler instead**, as it will replace `effector-react` with `effector-react/compat` in all modules, including those from third parties. Since `effector-react` uses `effector` under the hood, you need to use the compat-version of `effector` as well. Please, read effector/compat for details. ### Required Polyfills You need to install polyfills for these objects: * `Promise` * `Object.assign` * `Array.prototype.flat` * `Map` * `Set` In most cases, a bundler can automatically add polyfills. #### Vite
Vite Configuration Example ```js import { defineConfig } from "vite"; import legacy from "@vitejs/plugin-legacy"; export default defineConfig({ plugins: [ legacy({ polyfills: ["es.promise", "es.object.assign", "es.array.flat", "es.map", "es.set"], }), ], }); ```
## Usage ### Manual Usage You can use it instead of the `effector-react` package if you need to support old browsers. ```diff - import {useUnit} from 'effector-react' + import {useUnit} from 'effector-react/compat' ``` ### Automatic Replacement However, you can set up your bundler to automatically replace `effector` with `effector/compat` in your code. #### Webpack ```js module.exports = { resolve: { alias: { effector: "effector/compat", "effector-react": "effector-react/compat", }, }, }; ``` #### Vite ```js import { defineConfig } from "vite"; export default defineConfig({ resolve: { alias: { effector: "effector/compat", "effector-react": "effector-react/compat", }, }, }); ``` # effector-react/scope ## effector-react/scope ```ts import {} from "effector-react/scope"; ``` > WARNING Deprecated: > > Since [effector 23.0.0](https://changelog.effector.dev/#effector-23-0-0) the core team recommends using main module of `effector-react` instead. Provides all exports from effector-react, but enforces application to use Scope for all components. ### Usage You can use this module in the same way as effector-react, but it will require passing Scope to Provider component. ```jsx // main.js import { fork } from "effector"; import { Provider } from "effector-react/scope"; import React from "react"; import ReactDOM from "react-dom/client"; const scope = fork(); const root = ReactDOM.createRoot(document.getElementById("root")); root.render( , ); ``` ### Migration Since `effector-react/scope` is deprecated, it is better to migrate to effector-react by removing `scope` from import path. ```diff + import { Provider } from "effector-react"; - import { Provider } from "effector-react/scope"; ``` > WARNING Continues migration: > > `effector-react` and `effector-react/scope` do not share any code, so you have to migrate all your code to `effector-react` in the same time, because otherwise you will get runtime errors. These errors will be thrown because `effector-react` and `effector-react/scope` will use different instances `Provider` and do not have access to each other's `Provider`. If you use [Babel](https://babeljs.io/), you need to remove parameter reactSsr from `babel-plugin` configuration. ```diff { "plugins": [ [ "effector/babel-plugin", { - "reactSsr": true } ] ] } ``` If you use SWC, you need to remove [`bindings.react.scopeReplace`](https://github.com/effector/swc-plugin#bindings) parameter from `@effector/swc-plugin` configuration. ```diff { "$schema": "https://json.schemastore.org/swcrc", "jsc": { "experimental": { "plugins": [ "@effector/swc-plugin", { "bindings": { "react": { - "scopeReplace": true } } } ] } } } ``` ### Scope Enforcement All modern hooks of `effector-react` are designed to work with Scope. If you want to imitate the behavior of `effector-react/scope` module, you can use the second parameter of hooks with an option `forceScope: true`. In this case, the hook will throw an error if the Scope is not passed to Provider. ```diff - import { useUnit } from 'effector-react/scope' + import { useUnit } from 'effector-react' function Example() { - const { text } = useUnit({ text: $text }) + const { text } = useUnit({ text: $text }, { forceScope: true }) return

{text}

} ``` # useEvent ```ts import { useEvent } from "effector-react"; ``` > ERROR This is API is deprecated: > > Prefer useUnit hook instead. Bind event to current to use in dom event handlers.
Only `effector-react/scope` version works this way, `useEvent` of `effector-react` is a no-op and does not require `Provider` with scope. > INFO Note: > > Useful only if you have server-side rendering or writing tests for React-components. ## Methods ### `useEvent(unit)` #### Arguments 1. `unit` ( or ): Event or effect which will be bound to current `scope` #### Returns (Function): Function to pass to event handlers. Will trigger a given unit in the current scope. #### Examples ##### Basic Usage ```jsx import ReactDOM from "react-dom"; import { createEvent, createStore, fork } from "effector"; import { useStore, useEvent, Provider } from "effector-react"; const incremented = createEvent(); const $count = createStore(0); $count.on(incremented, (counter) => counter + 1); const App = () => { const count = useStore($count); const handleIncrement = useEvent(incremented); return ( <>

Count: {count}

); }; const scope = fork(); ReactDOM.render( , document.getElementById("root"), ); ``` Try it ### `useEvent(shape)` #### Arguments 1. `shape` Object or array of ( or ): Events or effects as values which will be bound to the current `scope` #### Returns (Object or Array): List of functions with the same names or keys as an argument to pass to event handlers. Will trigger a given unit in the current scope. #### Examples ##### Object Usage ```jsx import ReactDOM from "react-dom"; import { createStore, createEvent, fork } from "effector"; import { useStore, useEvent, Provider } from "effector-react"; const incremented = createEvent(); const decremented = createEvent(); const $count = createStore(0); $count.on(incremented, (counter) => counter + 1); $count.on(decremented, (counter) => counter - 1); const App = () => { const counter = useStore($count); const handler = useEvent({ incremented, decremented }); // or const [handleIncrement, handleDecrement] = useEvent([incremented, decremented]); return ( <>

Count: {counter}

); }; const scope = fork(); ReactDOM.render( , document.getElementById("root"), ); ``` # useGate ```ts import { useGate } from "effector-react"; ``` ## Methods ### `useGate(Gate, props?)` Hook for passing data to . #### Formulae ```ts const CustomGate: Gate; useGate(CustomGate, props?: T): void; ``` #### Arguments 1. `Gate` (Gate\) 2. `props` (`T`) #### Returns (`void`) #### Examples ##### Basic ```js import { createGate, useGate } from "effector-react"; import { Route } from "react-router"; const PageGate = createGate("page"); PageGate.state.watch(({ match }) => { console.log(match); }); const Home = (props) => { useGate(PageGate, props); return
Home
; }; const App = () => ; ``` # useList ```ts import { useList } from "effector-react"; ``` > INFO since: > > `useList` introduced in [effector-react 20.1.1](https://changelog.effector.dev/#effector-react-20-1-1) Hook function for efficient rendering of list store. Every item will be memoized and updated only when their data change. ## When should you use `useList`? `useList` is designed to solve the specific task of efficiently rendering lists. With `useList`, you don’t need to manually set `key` for list components, and it implements a more optimized re-rendering process. If you feel that something else is needed, it means the feature has outgrown `useList`, and you should use useStoreMap. With `useStoreMap`, you can extract specific data from the store in an optimal way, especially if you don’t need the entire store, but only a part of it ## API ### `useList($store, fn)` Using `index` as `key` for each element in the list. #### Formulae ```ts useList( $store: Store, fn: (value: T, index: number) => React.ReactNode, ): React.ReactNode; ``` #### Arguments 1. `$store` (Store\): Store with an array of items 2. `fn` (*Function*): Render function which will be called for every item in list #### Returns (`React.Node`) #### Examples ##### Basic ```jsx import { createStore } from "effector"; import { useList } from "effector-react"; const $users = createStore([ { id: 1, name: "Yung" }, { id: 2, name: "Lean" }, { id: 3, name: "Kyoto" }, { id: 4, name: "Sesh" }, ]); const App = () => { // we don't need keys here any more const list = useList($users, ({ name }, index) => (
  • [{index}] {name}
  • )); return
      {list}
    ; }; ``` Try it ##### With store updates ```jsx import { createStore, createEvent } from "effector"; import { useList, useUnit } from "effector-react"; const todoSubmitted = createEvent(); const todoToggled = createEvent(); const $todoList = createStore([ { text: "write useList example", done: true }, { text: "update readme", done: false }, ]); $todoList.on(todoToggled, (list, id) => list.map((todo, index) => { if (index === id) return { ...todo, done: !todo.done, }; return todo; }), ); $todoList.on(todoSubmitted, (list, text) => [...list, { text, done: false }]); todoSubmitted.watch((e) => { e.preventDefault(); }); const TodoList = () => { const [onTodoToggle] = useUnit([todoToggled]); return useList($todoList, ({ text, done }, index) => { const todo = done ? ( {text} ) : ( {text} ); return
  • onTodoToggle(index)}>{todo}
  • ; }); }; const App = () => { const [onTodoSubmit] = useUnit([todoSubmitted]); function handleSubmit(e) { e.preventDefault(); onTodoSubmit(e.currentTarget.elements.content.value); } return (

    todo list

    ); }; ``` Try it ### `useList($store, config)` Used when you need to pass dependencies to react (to update items when some of its dependencies are changed). By default, `useList` rerenders only when some of its items were changed. However, sometimes we need to update items when some external value (e.g. props field or state of another store) changes. In such case, we need to tell React about our dependencies and pass keys explicitly. #### Formulae ```ts useList( $store: Store, config: { keys: any[], getKey?: (value: T) => React.Key, fn: (value: T, index: number) => React.ReactNode, placeholder?: React.ReactNode, } ): React.ReactNode; ``` #### Arguments 1. `$store` (Store\): Store with an array of items 2. `config` (`Object`) * `keys` (`Array`): Array of dependencies, which will be passed to react by `useList` * `fn` (`(value: T) => React.ReactNode`): Render function which will be called for every item in list * `getKey` (`(value) => React.Key`): Optional function to compute key for every item of list * `placeholder` (`React.ReactNode`): Optional react node to render instead of an empty list > INFO since: > > `getKey` option introduced in [effector-react@21.3.0](https://changelog.effector.dev/#effector-react-21-3-0) > INFO since: > > `placeholder` option introduced in [effector-react@22.1.0](https://changelog.effector.dev/#effector-react-22-1-0) #### Returns (`React.Node`) #### Examples ##### Basic ```jsx import ReactDOM from "react-dom"; import { createEvent, createStore, restore } from "effector"; import { useUnit, useList } from "effector-react"; const renameUser = createEvent(); const $user = createStore("alice"); const $friends = createStore(["bob"]); $user.on(renameUser, (_, name) => name); const App = () => { const user = useUnit($user); return useList($friends, { keys: [user], fn: (friend) => (
    {friend} is a friend of {user}
    ), }); }; ReactDOM.render(, document.getElementById("root")); // =>
    bob is a friend of alice
    setTimeout(() => { renameUser("carol"); // =>
    bob is a friend of carol
    }, 500); ``` Try it # useProvidedScope ```ts import { useProvidedScope } from "effector-react"; ``` Low-level React Hook, which returns current Scope from Provider. > WARNING This is a Low-Level API: > > The `useProvidedScope` hook is a low-level API for library developers and **is not intended to be used in production code** directly. > > For production `effector-react` usage, see the useUnit hook. ## Methods ### `useProvidedScope()` #### Formulae ```ts useProvidedScope(): Scope | null ``` #### Returns (Scope | null) — if no Scope provided, returns `null`. #### Examples This hook can be used in library internals to handle various edge-cases, where `createWatch` and `scopeBind` APIs are also needed. For production code usage, see the useUnit hook instead. ```tsx const useCustomLibraryInternals = () => { const scope = useProvidedScope(); // ... }; ``` # useStore ```ts import { useStore } from "effector-react"; ``` React hook, which subscribes to a store and returns its current value, so when the store is updated, the component will update automatically. > ERROR This is API is deprecated: > > Prefer useUnit hook instead. ## Methods ### `useStore($store): State` #### Formulae ```ts useStore($store: Store): State ``` #### Arguments 1. `$store`: Store #### Returns (*`State`*): The value from the store #### Examples ```jsx import { createStore } from "effector"; import { useStore, useEvent } from "effector-react"; const $counter = createStore(0); const { incrementClicked, decrementClicked } = createApi($counter, { incrementClicked: (state) => state + 1, decrementClicked: (state) => state - 1, }); const App = () => { const counter = useStore($counter); const [onIncrement, onDecrement] = useEvent([incrementClicked, decrementClicked]); return (
    {counter}
    ); }; ``` Try it # useStoreMap ```ts import { useStoreMap } from "effector-react"; ``` > INFO since: > > `useStoreMap` introduced in [effector-react 19.1.2](https://changelog.effector.dev/#effector-react-19-1-2) React hook, which subscribes to a store and transforms its value with a given function. The component will update only when the selector function result will change. You can read the motivation in the [issue](https://github.com/effector/effector/issues/118). > WARNING Important: > > When the selector function returns `undefined`, the hook will skip the state update. > This can be problematic for example when working with optional properties. To handle such cases, use `defaultValue` option or transform `undefined` values in selector. ## Methods ### `useStoreMap($store, fn)` > INFO since: > > Short version of `useStoreMap` introduced in [effector-react@21.3.0](https://changelog.effector.dev/#effector-react-21-3-0) Common use case: subscribe to changes in selected part of store only #### Formulae ```ts useStoreMap( $store: Store, fn: (state: State) => Result, ): Result ``` #### Arguments 1. `$store`: Source Store\ 2. `fn` (`(state: State) => Result`): Selector function to receive part of source store #### Returns (`Result`): Value from the `fn` function call. #### Examples TBD ### `useStoreMap(config)` Overload used when you need to pass dependencies to react (to update items when some of its dependencies are changed) #### Formulae ```ts useStoreMap({ store: Store, keys: any[], fn: (state: State, keys: any[]) => Result, updateFilter?: (newResult: Result, oldResult: Result) => boolean, defaultValue?: Result, }): Result; ``` #### Arguments 1. `config` (*Object*): Configuration object * `store`: Source Store\ * `keys` (*Array*): This argument will be passed to React.useMemo to avoid unnecessary updates * `fn` (`(state: State, keys: any[]) => Result`): Selector function to receive part of source store * `updateFilter` (`(newResult, oldResult) => boolean`): *Optional* function used to compare old and new updates to prevent unnecessary rerenders. Uses createStore updateFilter option under the hood * `defaultValue`: Optional default value, used whenever `fn` returns undefined > INFO since: > > `updateFilter` option introduced in [effector-react@21.3.0](https://changelog.effector.dev/#effector-react-21-3-0) > INFO since: > > `defaultValue` option introduced in [effector-react@22.1.0](https://changelog.effector.dev/#effector-react-22-1-0) #### Returns (`Result`): Value from the `fn` function call, or the `defaultValue`. #### Examples ##### Basic This hook is useful for working with lists, especially with large ones ```jsx import { createStore } from "effector"; import { useList, useStoreMap } from "effector-react"; const usersRaw = [ { id: 1, name: "Yung", }, { id: 2, name: "Lean", }, { id: 3, name: "Kyoto", }, { id: 4, name: "Sesh", }, ]; const $users = createStore(usersRaw); const $ids = createStore(usersRaw.map(({ id }) => id)); const User = ({ id }) => { const user = useStoreMap({ store: $users, keys: [id], fn: (users, [userId]) => users.find(({ id }) => id === userId) ?? null, }); return (
    [{user.id}] {user.name}
    ); }; const UserList = () => { return useList($ids, (id) => ); }; ``` Try it # useUnit ```ts import { useUnit } from "effector-react"; ``` > INFO since: > > `useUnit` introduced in [effector-react 22.1.0](https://changelog.effector.dev/#effector-react-22-1-0) React hook, which takes any unit or shape of units. In the case of stores, it subscribes the component to the provided store and returns its current value, so when the store updates, the component will update automatically. In the case of events/effects – it binds to the current to use in DOM event handlers. Only the `effector-react/scope` version works this way; the `useUnit` of `effector-react` is no-op for events and does not require a `Provider` with scope. ## Methods ### `useUnit(unit)` Creates function that calls original unit but bounded to Scope if provided. #### Formulae ```ts useUnit(event: EventCallable): (payload: T) => T; useUnit(effect: Effect): (payload: Params) => Promise; ``` #### Arguments 1. `unit` (EventCallable\ or Effect\): Event or effect which will be bound to the current `scope`. #### Returns (Function): Function to pass to event handlers. Will trigger the given unit in the current scope. #### Examples ##### Basic ```jsx import { createEvent, createStore, fork } from "effector"; import { useUnit, Provider } from "effector-react"; import { render } from "react-dom"; const incrementClicked = createEvent(); const $count = createStore(0); $count.on(incrementClicked, (count) => count + 1); const App = () => { const [count, onIncrement] = useUnit([$count, incrementClicked]); return ( <>

    Count: {count}

    ); }; const scope = fork(); render( () => ( ), document.getElementById("root"), ); ``` ### `useUnit($store)` Reads value from the `$store` and rerenders component when `$store` updates in Scope if provided. #### Formulae ```ts useUnit($store: Store): T; ``` #### Arguments 1. `$store`: effector () #### Returns Current value of the store. #### Examples ##### Basic ```js import { createStore, createApi } from "effector"; import { useUnit } from "effector-react"; const $counter = createStore(0); const { incrementClicked, decrementClicked } = createApi($counter, { incrementClicked: (count) => count + 1, decrementClicked: (count) => count - 1, }); const App = () => { const counter = useUnit($counter); const [onIncrement, onDecrement] = useUnit([incrementClicked, decrementClicked]); return (
    {counter}
    ); }; ``` ### `useUnit(shape)` #### Formulae ```ts useUnit({ a: Store, b: Event, ... }): { a: A, b: (payload: B) => B; ... } useUnit([Store, Event, ... ]): [A, (payload: B) => B, ... ] ``` #### Arguments 1. `shape`: Object or array of (EventCallable, Effect, or Store) #### Returns (`Object` or `Array`): * If passed `EventCallable` or `Effect`: Functions with the same names or keys as the argument to pass to event handlers. Will trigger the given unit in the current scope.
    *Note: events or effects will be bound to `Scope` **only** if component wrapped into Provider.* * If passed `Store`: The current value of the store. #### Examples ##### Basic ```jsx import { createStore, createEvent, fork } from "effector"; import { useUnit, Provider } from "effector-react"; const incremented = createEvent(); const decremented = createEvent(); const $count = createStore(0); $count.on(incremented, (count) => count + 1); $count.on(decremented, (count) => count - 1); const App = () => { const count = useUnit($count); const on = useUnit({ incremented, decremented }); // or const [a, b] = useUnit([incremented, decremented]); return ( <>

    Count: {count}

    ); }; const scope = fork(); render( () => ( ), document.getElementById("root"), ); ``` # Effector Solid Gate *Gate* is a hook for conditional rendering, based on current value (or values) in props. An example of a problem that Gate can solve – you can put together all required data when component was mounted, or show another component if there is not enough data in props. Gate also looks good for Routing or animation. This allows you to send props back to *Store* to create a feedback loop. Gate can be used via the useGate hook or as a component with props (``). Gate stores and events can be used in the application as regular units. Gate can have two states: * **Open**, which means mounted * **Closed**, which means unmounted ## Properties ### `.state` Store > WARNING Important: > > Do not modify the `state` value! It is a derived store and should be kept in a predictable state. `Store`: Derived Store with the current state of the given gate. The state comes from the second argument of useGate and from props when rendering the gate as a component. ### `.open` Event > INFO Important: > > Do not manually call this event. It is an event that depends on a Gate's state. Event\: Event which will be called during the gate's mounting. ### `.close` Event > INFO Important: > > Do not manually call this event. It is an event that depends on a Gate's state. Event\: Event which will be called during the gate's unmounting. ### `.status` Store > WARNING Important: > > Do not modify the `status` value! It is a derived store and should be in a predictable state. `Store`: Boolean Derived Store, which shows if the given gate is mounted. # createGate ## Methods ### `createGate(config)` #### Formulae ```ts createGate(config): Gate ``` #### Arguments `config` (*Object*): Optional configuration object * `defaultState?`: Optional default state for Gate.state * `domain?` (\[*Domain*]/apieffector/Domain)): Optional domain which will be used to create gate units (Gate.open event, Gate.state store and so on) * `name?` (*string*): Optional name which will be used as name of a created Solid component #### Returns #### Examples TBD ### `createGate(name?)` #### Formulae ```ts createGate(name): Gate ``` #### Arguments 1. `name?` (*string*): Optional name which will be used as name of a created Solid component #### Returns #### Examples ##### Basic usage ```js import { createGate } from "effector-solid"; import { render } from "solid-js/web"; const Gate = createGate("gate with props"); const App = () => (
    ); Gate.state.watch((state) => { console.log("current state", state); }); // => current state {} const unmount = render(() => , document.getElementById("root")); // => current state {foo: 'bar'} unmount(); // => current state {} ``` # effector-solid Effector bindings for SolidJS. ## Reactive Helpers * useUnit(unit) * useStoreMap({ store, keys, fn }) ## Gate API * Gate * createGate() * useGate(GateComponent, props) ## Import Map Package `effector-solid` provides couple different entry points for different purposes: * effector-solid/scope # effector-solid/scope ## effector-solid/scope ```ts import {} from "effector-solid/scope"; ``` > WARNING Deprecated: > > Since [effector 23.0.0](https://changelog.effector.dev/#effector-23-0-0) the core team recommends using the main module of `effector-solid` instead. Provides all exports from effector-solid, but enforces the application to use Scope for all components. ### Usage You can use this module in the same way as effector-solid, but it will require passing Scope to Provider component. ```jsx // main.js import { fork } from "effector"; import { Provider } from "effector-solid/scope"; import { render } from "solid-js/web"; const scope = fork(); render( , document.getElementById("root"), ); ``` ### Migration Since `effector-solid/scope` is deprecated, it is recommended to migrate to effector-solid by removing `scope` from the import path. ```diff + import { Provider } from "effector-solid"; - import { Provider } from "effector-solid/scope"; ``` > WARNING Continued migration: > > `effector-solid` and `effector-solid/scope` do not share any code, so you have to migrate all your code to `effector-solid` at the same time, because otherwise, you will get runtime errors. These errors will occur because `effector-solid` and `effector-solid/scope` will use different instances of `Provider` and do not have access to each other's `Provider`. ### Scope enforcement All modern hooks of `effector-solid` are designed to work with Scope. If you want to imitate the behavior of the `effector-solid/scope` module, you can pass a second parameter to hooks with an option `forceScope: true`. In this case, the hook will throw an error if the Scope is not passed to Provider. ```diff - import { useUnit } from 'effector-solid/scope' + import { useUnit } from 'effector-solid' function MyComponent() { - const { test } = useUnit({ text: $text }) + const { test } = useUnit({ text: $text }, { forceScope: true }) return

    {text}

    } ``` # useGate ```ts import { useGate } from "effector-solid"; ``` Function for passing data to . ## Methods ### `useGate(Gate, props)` #### Formulae ```ts useGate(Gate: Gate, props: Props): void; ``` #### Arguments 1. `Gate` (Gate\) 2. `props` (*Props*) #### Returns (`void`) #### Examples ##### Basic Usage ```jsx import { createGate, useGate } from "effector-solid"; import { Route, Routes } from "solid-app-router"; const PageGate = createGate("page"); const Home = (props) => { useGate(PageGate, props); return
    Home
    ; }; PageGate.state.watch(({ match }) => { console.log(match); }); const App = () => ( } /> ); ``` # useStoreMap ```ts import { useStoreMap } from "effector-solid"; ``` ## Methods ### `useStoreMap($store, fn)` Function, which subscribes to a store and transforms its value with a given function. Signal will update only when the selector function result will change. Common use case: subscribe to changes in selected part of store only. #### Formulae ```ts useStoreMap( $store: Store, fn: (state: State) => Result, ): Accessor; ``` #### Arguments 1. `$store`: Source Store\ 2. `fn` (`(state: T) => Result`): Selector function to receive part of source store #### Returns (`Result`) #### Examples TBD ### `useStoreMap(config)` #### Formulae ```ts useStoreMap({ store: Store, keys: any[], fn: (state: State, keys: any[]) => Result, updateFilter? (newResult, oldResult) => boolean, }): Result; ``` #### Arguments 1. `params` (*Object*): Configuration object * `store`: Source store * `keys` (*Array*): Will be passed to `fn` selector * `fn` (*(state, keys) => result*): Selector function to receive part of the source store * `updateFilter` (*(newResult, oldResult) => boolean*): *Optional* function used to compare old and new updates to prevent unnecessary rerenders. Uses createStore updateFilter option under the hood #### Returns (`Accessor`) #### Examples This hook is very useful for working with lists, especially large ones. ```jsx import { createStore } from "effector"; import { useUnit, useStoreMap } from "effector-solid"; import { For } from "solid-js/web"; const usersRaw = [ { id: 1, name: "Yung", }, { id: 2, name: "Lean", }, { id: 3, name: "Kyoto", }, { id: 4, name: "Sesh", }, ]; const $users = createStore(usersRaw); const $ids = createStore(usersRaw.map(({ id }) => id)); const User = ({ id }) => { const user = useStoreMap({ store: $users, keys: [id], fn: (users, [userId]) => users.find(({ id }) => id === userId) ?? null, }); return (
    [{user()?.id}] {user()?.name}
    ); }; const UserList = () => { const ids = useUnit($ids); return {(id) => }; }; ``` # useUnit ```ts import { useUnit } from "effector-solid"; ``` Binds effector stores to the Solid reactivity system or, in the case of events/effects – binds to current to use in dom event handlers. Only `effector-solid/scope` version works this way, `useUnit` of `effector-solid` is no-op for events and does not require `Provider` with scope. ## Methods ### `useUnit(unit)` #### Arguments ```ts useUnit(event: EventCallable): (payload: T) => T; useUnit(effect: Effect): (payload: Params) => Promise; ``` #### Arguments 1. `unit` (EventCallable\ or Effect\): Event or effect which will be bound to current `scope`. #### Returns (`Function`): Function to pass to event handlers. Will trigger the given unit in the current scope. #### Example A basic Solid component using `useUnit` with events and stores. ```jsx import { render } from "solid-js/web"; import { createEvent, createStore, fork } from "effector"; import { useUnit, Provider } from "effector-solid"; const incremented = createEvent(); const $count = createStore(0); $count.on(incremented, (count) => count + 1); const App = () => { const [count, handleIncrement] = useUnit([$count, incremented]); return ( <>

    Count: {count()}

    ); }; const scope = fork(); render( () => ( ), document.getElementById("root"), ); ``` ### `useUnit(store)` #### Formulae ```ts useUnit($store: Store): Accessor; ``` #### Arguments 1. `$store` effector (). #### Returns (`Accessor`) which will subscribe to store state. #### Example ```jsx import { createStore, createApi } from "effector"; import { useUnit } from "effector-solid"; const $counter = createStore(0); const { incremented, decremented } = createApi($counter, { incremented: (count) => count + 1, decremented: (count) => count - 1, }); const App = () => { const counter = useUnit($counter); const [handleIncrement, handleDecrement] = useUnit([incremented, decremented]); return (
    {counter()}
    ); }; ``` ### `useUnit(shape)` #### Formulae ```ts useUnit({ a: Store
    , b: Event, ... }): { a: Accessor, b: (payload: B) => B; ... } useUnit([Store, Event, ... ]): [Accessor, (payload: B) => B, ... ] ``` #### Arguments 1. `shape` Object or array of (EventCallable, Effect, or Store): Events, or effects, or stores as accessors which will be bound to the current `scope`. #### Returns (`Object` or `Array`): * If `EventCallable` or `Effect`: functions with the same names or keys as argument to pass to event handlers. Will trigger given unit in current scope *Note: events or effects will be bound **only** if `useUnit` is imported from `effector-solid/scope`*. * If `Store`: accessor signals which will subscribe to the store state. #### Examples ```jsx import { render } from "solid-js/web"; import { createStore, createEvent, fork } from "effector"; import { useUnit, Provider } from "effector-solid/scope"; const incremented = createEvent(); const decremented = createEvent(); const $count = createStore(0) .on(incremented, (count) => count + 1) .on(decremented, (count) => count - 1); const App = () => { const count = useUnit($count); const on = useUnit({ incremented, decremented }); // or const [a, b] = useUnit([incremented, decremented]); return ( <>

    Count: {count()}

    ); }; const scope = fork(); render( () => ( ), document.getElementById("root"), ); ``` # ComponentOptions ## ComponentOptions (Vue2) ### `effector` #### Returns (*`Function | Object | Store`*): `Store` or object of `Store`'s, or function which will be called with the Component instance as `this`. #### Examples ##### Basic Usage ```js import Vue from "vue"; import { createStore, combine } from "effector"; const counter = createStore(0); new Vue({ data() { return { foo: "bar", }; }, effector() { // would create `state` in template return combine( this.$store(() => this.foo), counter, (foo, counter) => `${foo} + ${counter}`, ); }, }); ``` ##### Using Object Syntax ```js import { counter } from "./stores"; new Vue({ effector: { counter, // would create `counter` in template }, }); ``` ##### Using Store Directly ```js import { counter } from "./stores"; new Vue({ effector: counter, // would create `state` in template }); ``` # EffectorScopePlugin The Plugin provides a general scope which needs for read and update effector's stores, call effector's events. Required for SSR. ## Plugins ### `EffectorScopePlugin({ scope, scopeName })` #### Arguments 1. `scope` Scope 2. `scopeName?` custom scopeName (default: `root`) #### Examples ##### Basic Usage ```js import { createSSRApp } from "vue"; import { EffectorScopePlugin } from "effector-vue"; import { fork } from "effector"; const app = createSSRApp(AppComponent); const scope = fork(); app.use( EffectorScopePlugin({ scope, scopeName: "app-scope-name", }), ); ``` # Effector Vue Gate *Gate* is a hook for conditional rendering, based on current value (or values) in props. An example of a problem that Gate can solve – you can put together all required data, when component was mounted. This allows you to send props back to *Store* to create feedback loop. Gate can be used via useGate hook. Gate stores and events can be used in the application as regular units Gate can have two states: * **Open**, which means mounted * **Closed**, which means unmounted ## Gate Properties ### `.state` > WARNING Important: > > Do not modify `state` value! It is derived store and should be in predictable state. `Store`: DerivedStore with current state of the given gate. The state comes from the second argument of useGate and from props when rendering gate as a component. ### `.open` > INFO Important: > > Do not manually call this event. It is an event that depends on a Gate state. Event\: Event which will be called during gate mounting ### `.close` > INFO Important: > > Do not manually call this event. It is an event that depends on a Gate state. Event\: Event which will be called during a gate unmounting. ### `.status` > WARNING Important: > > Do not modify `status` value! It is derived store and should be in predictable state. `Store`: Boolean DerivedStore, which show if given gate is mounted. # VueEffector ```ts import { VueEffector } from "effector-vue/options-vue3"; ``` `effector-vue` plugin for vue 3 creates a mixin that takes a binding function from the effector option. ## Methods ### `VueEffector(app)` #### Arguments 1. `app` (*instance Vue*): Vue instance #### Returns (*`void`*) #### Examples ##### Installation plugin ```js import { createApp } from "vue"; import { VueEffector } from "effector-vue/options-vue3"; import App from "./App.vue"; const app = createApp(App); app.use(VueEffector); ``` ##### Effector options ```html ``` ```js import { $user, create, createFx } from 'model' export default { name: 'VueComponent', effector: () => ({ user: $user, createDone: createFx.done, createPending: createFx.pending, }), watch: { createDone() { // do something after the effect is done } }, methods: { create, // template binding createFx, }, ... } ``` # VueEffector ```ts import { VueEffector } from "effector-vue"; ``` `effector-vue` plugin for vue 2 ## Methods ### `VueEffector(Vue, options?)` #### Arguments 1. `Vue` (*class Vue*): Vue class 2. `options` (*Object*): Plugin options * TBD #### Returns (*`void`*) #### Examples ```js import Vue from "vue"; import { VueEffector } from "effector-vue"; Vue.use(VueEffector); ``` # VueSSRPlugin The Plugin provides a general scope which needs for read and update effector's stores, call effector's events. Required for SSR. ## Plugins ### `VueSSRPlugin({ scope, scopeName })` > WARNING Deprecated: > > Since [effector 23.0.0](https://changelog.effector.dev/#effector-23-0-0) `VueSSRPlugin` is deprecated. Use EffectorScopePlugin instead. ### Arguments 1. `scope` Scope 2. `scopeName?` custom scopeName (default: `root`) ### Examples #### Basic usage ```js import { createSSRApp } from "vue"; import { VueSSRPlugin } from "effector-vue/ssr"; import { fork } from "effector"; const app = createSSRApp(AppComponent); const scope = fork(); app.use( VueSSRPlugin({ scope, scopeName: "app-scope-name", }), ); ``` # createComponent ## Methods ### `createComponent(options, store?)` #### Arguments 1. `options` (*Object*): component options (hooks, methods, computed properties) 2. `store` (*Object*): Store object from effector #### Returns (*`vue component`*) #### Example ```html ``` ```js // component.vue import { createComponent } from "effector-vue"; const $counter = createStore(0); const { update } = createApi($counter, { update: (_, value: number) => value, }); export default createComponent( { name: "Counter", methods: { update, handleClick() { const value = this.$counter + 1; // this.$counter <- number ( typescript tips ) this.update(value); }, }, }, { $counter }, ); ``` # createGate Creates a to consume data from view, designed for vue 3. If `defaultState` is defined, Gate.state will be created with passed value. ## Methods ### `createGate(config?: {defaultState?, domain?, name?})` #### Arguments `config` (*Object*): Optional configuration object * `defaultState?`: Optional default state for Gate.state * `domain?` (): Optional domain which will be used to create gate units (Gate.open event, Gate.state store, and so on) * `name?` (*string*): Optional name which will be used as the name of a created Vue component #### Returns #### Examples ##### Basic Usage ```js import { createGate, useGate } from "effector-vue/composition"; const ListGate = createGate({ name: "Gate with required props", }); const ListItem = { template: `
    {{id}}
    `, props: { id: { type: String, required: true, }, }, setup(props) { useGate(ListGate, () => props.id); }, }; const app = { template: `
    `, components: { ListItem, }, setup() { const id = ref("1"); return { id }; }, }; Gate.state.watch((state) => { console.log("current state", state); }); // => current state null app.mount("#app"); // => current state 1 app.unmount(); // => current state null ``` # effector-vue Effector binginds for Vue. ## Top-Level Exports * VueEffector(Vue, options?) * createComponent(ComponentOptions, store?) * EffectorScopePlugin({scope, scopeName?}) ## ComponentOptions API * ComponentOptions\ ## Hooks * useUnit(shape) * useStore(store) * useStoreMap({store, keys, fn}) * useVModel(store) ## Gate API * Gate * createGate() * useGate(GateComponent, props) ## Import map Package `effector-vue` provides couple different entry points for different purposes: * effector-vue/composition * effector-vue/ssr # effector-vue/composition ## effector-vue/composition ```ts import {} from "effector-vue/composition"; ``` Provides additional API for effector-vue that allows to use [Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html) ### APIs * useUnit(shape) * useStore($store) * useStoreMap({ store, keys, fn }) * useVModel($store) # effector-vue/ssr ## effector-vue/ssr ```ts import {} from "effector-vue/ssr"; ``` > WARNING Deprecated: > > Since [effector 23.0.0](https://changelog.effector.dev/#effector-23-0-0) the core team recommends using main module of `effector-vue` of `effector-vue/composition` instead. Provides additional API for effector-vue that enforces library to use Scope ### APIs * useEvent(event) * VueSSRPlugin # useEvent ```ts import { useEvent } from "effector-vue/ssr"; ``` > WARNING Deprecated: > > Since [effector 23.0.0](https://changelog.effector.dev/#effector-23-0-0) `useEvent` is deprecated. Use useUnit instead. Bind event to current fork instance to use in dom event handlers. Used **only** with ssr, in application without forks `useEvent` will do nothing ## Methods ### `useEvent(unit)` #### Arguments 1. `unit` ( or ): Event or effect which will be bound to current `scope` #### Returns (`Function`): Function to pass to event handlers. Will trigger a given unit in current scope #### Examples ##### Basic ```js import { createStore, createEvent } from "effector"; import { useEvent } from "effector-vue/ssr"; const incremented = createEvent(); const $count = createStore(0); $count.on(incremented, (x) => x + 1); export default { setup() { const counter = useStore($count); const onIncrement = useEvent(incremented); return { onIncrement, counter, }; }, }; ``` # useGate ```ts import { useGate } from "effector-vue/composition"; ``` ## Methods ### `useGate(Gate, props)` Using a Gate to consume data from view. Designed for Vue 3 #### Arguments 1. `Gate` () 2. `props` (*Props*) #### Returns (*`void`*) #### Examples See example # useStore ## useStore ```ts import { useStore } from "effector-vue/composition"; ``` A hook function, which subscribes to watcher, that observes changes in the current **readonly** store, so when recording results, the component will update automatically. You can mutate the store value **only via createEvent**. Designed for vue 3 ### `useStore($store)` #### Arguments 1. `$store` (Store\) #### Returns (`readonly(State)`) #### Example ```js import { createStore, createApi } from "effector"; import { useStore } from "effector-vue/composition"; const $counter = createStore(0); const { incremented, decremented } = createApi($counter, { incremented: (count) => count + 1, decremented: (count) => count - 1, }); export default { setup() { const counter = useStore($counter); return { counter, incremented, decremented, }; }, }; ``` # useStoreMap ```ts import { useStoreMap } from "effector-vue/composition"; ``` Function, which subscribes to store and transforms its value with a given function. Signal will update only when the selector function result will change ## Methods ### `useStoreMap($store, fn)` #### Formulae ```ts useStoreMap( $store: Store, fn: (state: State) => Result, ): ComputedRef; ``` #### Arguments 1. `$store`: Source Store\ 2. `fn` (*(state) => result*): Selector function to receive part of source store #### Returns (`ComputedRef`) ### `useStoreMap(config)` #### Formulae ```ts useStoreMap({ store: Store, keys?: () => Keys, fn: (state: State, keys: Keys) => Result, defaultValue?: Result, }): ComputedRef; ``` #### Arguments 1. `params` (*Object*): Configuration object * `store`: Source store * `keys` (`() => Keys`): Will be passed to `fn` selector * `fn` (`(state: State, keys: Keys) => Result`): Selector function to receive part of source store * `defaultValue` (`Result`): Optional default value if `fn` returned `undefined` #### Returns (`ComputedRef`) #### Examples This hook is very useful for working with lists, especially with large ones ###### User.vue ```js import { createStore } from "effector"; import { useUnit, useStoreMap } from "effector-vue/composition"; const $users = createStore([ { id: 1, name: "Yung", }, { id: 2, name: "Lean", }, { id: 3, name: "Kyoto", }, { id: 4, name: "Sesh", }, ]); export default { props: { id: Number, }, setup(props) { const user = useStoreMap({ store: $users, keys: () => props.id, fn: (users, userId) => users.find(({ id }) => id === userId), }); return { user }; }, }; ``` ```jsx
    [{user.id}] {user.name}
    ``` ###### App.vue ```js const $ids = createStore(data.map(({ id }) => id)); export default { setup() { const ids = useStore($ids); return { ids }; }, }; ``` ```jsx
    ``` # useUnit ```ts import { useUnit } from "effector-vue/composition"; ``` Bind to Vue reactivity system or, in the case of / - bind to current to use in DOM event handlers. **Designed for Vue 3 and Composition API exclusively.** > INFO Future: > > This API can completely replace the following APIs: > > * useStore($store) > * useEvent(event) > > In the future, these APIs can be deprecated and removed. ## Methods ### `useUnit(unit)` #### Arguments 1. `unit` ( or ): Event or effect which will be bound to current #### Returns (`Function`): Function to pass to event handlers. Will trigger given unit in current scope #### Examples ##### Basic Usage ```js // model.js import { createEvent, createStore, fork } from "effector"; const incremented = createEvent(); const $count = createStore(0); $count.on(incremented, (count) => count + 1); ``` ```html // App.vue ``` #### `useUnit($store)` ##### Arguments 1. `$store` (): Store which will be bound to Vue reactivity system ##### Returns Reactive value of given ##### Examples ###### Basic Usage ```js // model.js import { createEvent, createStore, fork } from "effector"; const incremented = createEvent(); const $count = createStore(0); $count.on(incremented, (count) => count + 1); ``` ```html // App.vue ``` #### `useUnit(shape)` ##### Arguments 1. `shape` Object or array of ( or or ): Every unit will be processed by `useUnit` and returned as a reactive value in case of or as a function to pass to event handlers in case of or . ##### Returns (Object or Array): * if or : functions with the same names or keys as argument to pass to event handlers. Will trigger given unit in current . * if : reactive value of given with the same names or keys as argument. ##### Examples ###### Basic Usage ```js // model.js import { createEvent, createStore, fork } from "effector"; const incremented = createEvent(); const $count = createStore(0); $count.on(incremented, (count) => count + 1); ``` ```html // App.vue ``` # useVModel ```ts import { useVModel } from "effector-vue/composition"; ``` A hook function, which subscribes to a watcher that observes changes in the current store, so when recording results, the component will automatically update. It is primarily used when working with forms (`v-model`) in Vue 3. ## Methods ### `useVModel($store)` #### Formulae ```ts useVModel($store: Store): Ref>; ``` Designed for Vue 3. #### Arguments 1. `$store` () 2. `shape of Stores` () #### Returns (`State`) #### Examples ##### Single Store ```js import { createStore, createApi } from "effector"; import { useVModel } from "effector-vue/composition"; const $user = createStore({ name: "", surname: "", skills: ["CSS", "HTML"], }); export default { setup() { const user = useVModel($user); return { user }; }, }; ``` ```html
    ``` ##### Store Shape ```js import { createStore, createApi } from "effector"; import { useVModel } from "effector-vue/composition"; const $name = createStore(""); const $surname = createStore(""); const $skills = createStore([]); const model = { name: $name, surname: $surname, skills: $skills, }; export default { setup() { const user = useVModel(model); return { user }; }, }; ``` ```html
    ``` # Domain ```ts import { type Domain } from "effector"; ``` Domain is a namespace for your events, stores and effects. Domain can subscribe to event, effect, store or nested domain creation with `onCreateEvent`, `onCreateStore`, `onCreateEffect`, `onCreateDomain` methods. It is useful for logging or other side effects. ## Unit creators > INFO since: > > [effector 20.7.0](https://changelog.effector.dev/#effector-20-7-0) ### `createEvent(name?)` #### Arguments 1. `name`? (*string*): event name #### Returns : New event ### `createEffect(handler?)` Creates an effect with given handler. #### Arguments 1. `handler`? (*Function*): function to handle effect calls, also can be set with use(handler) #### Returns : A container for async function. > INFO since: > > [effector 21.3.0](https://changelog.effector.dev/#effector-21-3-0) ### `createEffect(name?)` #### Arguments 1. `name`? (*string*): effect name #### Returns : A container for async function. ### `createStore(defaultState)` #### Arguments 1. `defaultState` (*State*): store default state #### Returns : New store ### `createDomain(name?)` #### Arguments 1. `name`? (*string*): domain name #### Returns : New domain ### Aliases #### `event(name?)` An alias for domain.createEvent #### `effect(name?)` An alias for domain.createEffect #### `store(defaultState)` An alias for domain.createStore #### `domain(name?)` An alias for domain.createDomain ## Domain Properties ### `.history` Contains mutable read-only sets of units inside a domain. > INFO since: > > [effector 20.3.0](https://changelog.effector.dev/#effector-20-3-0) #### Formulae ```ts interface DomainHistory { stores: Set>; events: Set>; domains: Set; effects: Set>; } const { stores, events, domains, effects } = domain.history; ``` When any kind of unit created inside a domain, it appears in a set with the name of type(stores, events, domains, effects) in the same order as created. #### Examples ##### Basic ```js import { createDomain } from "effector"; const domain = createDomain(); const eventA = domain.event(); const $storeB = domain.store(0); console.log(domain.history); // => {stores: Set{storeB}, events: Set{eventA}, domains: Set, effects: Set} ``` Try it ## Domain hooks ### `onCreateEvent(callback)` #### Formulae ```ts domain.onCreateEvent((event: Event) => {}); ``` * Function passed to `onCreateEvent` called every time, as new event created in `domain` * Function called with `event` as first argument * The result of function call is ignored #### Arguments 1. `callback` ([*Watcher*][_Watcher_]): A function that receives Event and will be called during every domain.createEvent call #### Returns [*Subscription*][_Subscription_]: Unsubscribe function. #### Example ```js import { createDomain } from "effector"; const domain = createDomain(); domain.onCreateEvent((event) => { console.log("new event created"); }); const a = domain.createEvent(); // => new event created const b = domain.createEvent(); // => new event created ``` Try it ### `onCreateEffect(callback)` #### Formulae ```ts domain.onCreateEffect((effect: Effect) => {}); ``` * Function passed to `onCreateEffect` called every time, as new effect created in `domain` * Function called with `effect` as first argument * The result of function call is ignored #### Arguments 1. `callback` ([*Watcher*][_Watcher_]): A function that receives Effect and will be called during every domain.createEffect call #### Returns [*Subscription*][_Subscription_]: Unsubscribe function. #### Example ```js import { createDomain } from "effector"; const domain = createDomain(); domain.onCreateEffect((effect) => { console.log("new effect created"); }); const fooFx = domain.createEffect(); // => new effect created const barFx = domain.createEffect(); // => new effect created ``` Try it ### `onCreateStore(callback)` #### Formulae ```ts domain.onCreateStore(($store: Store) => {}); ``` * Function passed to `onCreateStore` called every time, as new store created in `domain` * Function called with `$store` as first argument * The result of function call is ignored #### Arguments 1. `callback` ([*Watcher*][_Watcher_]): A function that receives Store and will be called during every domain.createStore call #### Returns [*Subscription*][_Subscription_]: Unsubscribe function. #### Example ```js import { createDomain } from "effector"; const domain = createDomain(); domain.onCreateStore((store) => { console.log("new store created"); }); const $a = domain.createStore(null); // => new store created ``` Try it ### `onCreateDomain(callback)` #### Formulae ```ts domain.onCreateDomain((domain) => {}); ``` * Function passed to `onCreateDomain` called every time, as subdomain created in `domain` * Function called with `domain` as first argument * The result of function call is ignored #### Arguments 1. `callback` ([*Watcher*][_Watcher_]): A function that receives Domain and will be called during every domain.createDomain call #### Returns [*Subscription*][_Subscription_]: Unsubscribe function. #### Example ```js import { createDomain } from "effector"; const domain = createDomain(); domain.onCreateDomain((domain) => { console.log("new domain created"); }); const a = domain.createDomain(); // => new domain created const b = domain.createDomain(); // => new domain created ``` Try it [_watcher_]: /en/explanation/glossary#watcher [_subscription_]: /en/explanation/glossary#subscription # Effect API [eventTypes]: /en/api/effector/Event#event-types [storeTypes]: /en/essentials/typescript#store-types ## Effect API ```ts import { type Effect, createEffect } from "effector"; const effectFx = createEffect(); ``` An Effect is a unit designed to handle side effects, whether synchronous or asynchronous. It includes a set of pre-built events and stores that streamline common operations. It is categorized as a unit. Effects can be called like regular functions (*imperative call*) and can also be connected along with their properties to various API methods including sample and split (*declarative connection*). > TIP effective effect: > > If you're not familiar with effects and how to work with them, check out Asynchronous Operations in effector using Effects. ### Effect Interface Available methods and properties of effects: |
    Method/Property
    | Description | | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | use(handler) | Replaces the effect's handler with a new `handler` function. | | use.getCurrent() | Returns the current effect handler. | | watch(watcher) | Adds a listener that calls `watcher` on each effect invocation. | | map(fn) | Creates a new [derived event][eventTypes] that triggers when the effect is called with the result of calling `fn` on the effect's parameters. | | prepend(fn) | Creates a new [event][eventTypes] that transforms input data through `fn` before calling the effect. | | filterMap(fn) | Creates a new [derived event][eventTypes] that triggers when the effect is called with the result of fn, if it didn't return `undefined`. | | done | [Derived event][eventTypes] that triggers when the effect completes successfully with params and result. | | doneData | [Derived event][eventTypes] with the result of successful effect execution with result. | | fail | [Derived event][eventTypes] that triggers when the effect execution fails with params and error. | | failData | [Derived event][eventTypes] with the effect's error data. | | finally | [Derived event][eventTypes] that triggers on any effect completion. | | pending | [Derived store][storeTypes] `Store` with the effect execution status (`true` during execution). | | inFlight | [Derived store][storeTypes] `Store` with the count of active effect calls. | | sid | Unique identifier of the unit. | | shortName | String property containing the variable name in which the effect was declared. | | compositeName | Composite effect name (including domain and short name) — useful for logging and tracing. | ### Effect Peculiarities 1. When called imperatively, they always return a promise reflecting the side effect execution progress. 2. Effects accept only one argument, just like events. 3. They have built-in stores (pending, inFlight) and events (done, fail, finally, etc.) for convenience. ### Effect Methods #### `.use(handler)` > WARNING use is an anti-pattern: > > If the implementation value is known immediately, it's better to use `createEffect(handler)`. > > The `use(handler)` method is an anti-pattern that degrades type inference. Defines the effect implementation: the function that will be called when the effect is triggered. Used for cases when the implementation is not set during creation or when testing requires changing the effect's behavior.
    Accepts a `params` argument, which is the data with which the effect was called. > INFO use takes priority: > > If the effect already had an implementation at the time of call, it will be replaced with the new one. * **Formula** ```ts const fx: Effect; fx.use(handler); ``` * **Type** ```ts effect.use(handler: (params: Params) => Promise | Done): Effect< Params, Done, Fail > ``` * **Examples** ```js import { createEffect } from "effector"; const fetchUserReposFx = createEffect(); fetchUserReposFx.use(async ({ name }) => { console.log("fetchUserReposFx called for github user", name); const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }); await fetchUserReposFx({ name: "zerobias" }); // => fetchUserReposFx called for github user zerobias ``` Run example * **Return value** Returns the current effect. *** #### `.use.getCurrent()` Method for getting the current effect implementation. Used for testing. If the effect doesn't have an implementation set yet, a default function will be returned that throws an error when called. * **Formula** ```ts const fx: Effect; const handler = fx.use.getCurrent(); ``` * **Type** ```ts effect.use.getCurrent(): (params: Params) => Promise ``` * **Examples** ```js const handlerA = () => "A"; const handlerB = () => "B"; const fx = createEffect(handlerA); console.log(fx.use.getCurrent() === handlerA); // => true fx.use(handlerB); console.log(fx.use.getCurrent() === handlerB); // => true ``` Run example * **Return value** Returns the effect's implementation function that was set through createEffect or using the use method. *** #### `.watch(watcher)` Calls an additional function with side effects on each effect trigger. Shouldn't be used for logic, better to replace with sample. * **Formula** ```ts const fx: Effect; const unwatch = fx.watch(watcher); ``` * **Type** ```ts effect.watch(watcher: (payload: Params) => any): Subscription ``` * **Examples** ```js import { createEffect } from "effector"; const fx = createEffect((params) => params); fx.watch((params) => { console.log("effect called with argument", params); }); await fx(10); // => effect called with argument 10 ``` Run example * **Return value** Subscription cancellation function, after calling it the `watcher` stops receiving updates and is removed from memory. *** #### `.map(fn)` The map method creates a [derived event][eventTypes]. The event is triggered at the moment the effect is executed, using the same arguments as the effect and the result returned by the `fn` function. Works similarly to Event.map(fn). * **Formula** ```ts const fx: Effect; const eventB = fx.map(fn); ``` * **Type** ```ts effect.map(fn: (params: Params) => T): Event ``` * **Examples** ```ts import { createEffect } from "effector"; interface User { // ... } const saveUserFx = createEffect(async ({ id, name, email }: User) => { // ... return response.json(); }); const userNameSaving = saveUserFx.map(({ name }) => { console.log("Starting user save: ", name); return name; }); const savingNotification = saveUserFx.map(({ name, email }) => { console.log("Save notification"); return `Saving user: ${name} (${email})`; }); // When calling the effect, both derived events will trigger await saveUserFx({ id: 1, name: "John", email: "john@example.com" }); // => Starting user save: John // => Saving user: John (john@example.com) ``` Run example * **Return value** Returns a new [derived event][eventTypes]. *** #### `.prepend(fn)` Creates a new event to transform data *before* running the effect. Compared to map, it works in the opposite direction. Works similarly to Event.prepend(fn). * **Formula** ```ts const fx: Effect; const trigger = fx.prepend(fn); ``` * **Type** ```ts effect.prepend(fn: (_: Before) => Params): EventCallable ``` * **Examples** ```js import { createEffect } from "effector"; const saveFx = createEffect(async (data) => { console.log("saveFx called with:", data); await api.save(data); }); // create a trigger event for the effect const saveForm = saveFx.prepend((form) => ({ ...form, modified: true, })); saveForm({ name: "John", email: "john@example.com" }); // => saveFx called with: { name: "John", email: "john@example.com", modified: true } ``` * **Return value** Returns a new [event][eventTypes]. *** #### `.filterMap(fn)` The `filterMap` method creates a [derived event][eventTypes]. The `fn` function computation runs simultaneously with the effect, however if the function returns `undefined`, the event doesn't trigger. Works similarly to the .map(fn) method, but with filtering by return value. * **Formula** ```ts const fx: Effect; const filtered = fx.filterMap(fn); ``` * **Type** ```ts effect.filterMap(fn: (payload: Params) => T | undefined): Event ``` * **Examples** ```ts import { createEffect } from "effector"; const validateAndSaveFx = createEffect(async (userData) => { if (!userData.isValid) { throw new Error("Invalid data"); } return await saveToDatabase(userData); }); // Create event only for valid data const validDataProcessing = validateAndSaveFx.filterMap((userData) => { if (userData.isValid && userData.priority === "high") { return { id: userData.id, timestamp: Date.now(), }; } // If data is invalid or priority is not high, the event won't trigger }); validDataProcessing.watch(({ id, timestamp }) => { console.log(`Processing high-priority data ID: ${id} at ${timestamp}`); }); // Example calls await validateAndSaveFx({ id: 1, isValid: true, priority: "high", role: "user", }); // => Processing high-priority data ID: 1 at 1703123456789 ``` * **Return value** Returns a new [derived event][eventTypes]. ### Effect Properties #### `.done` [Derived event][eventTypes] that triggers with the result of effect execution and the argument passed during the call. * **Type** ```ts interface Effect { done: Event<{ params: Params; result: Done }>; } ``` * **Examples** ```js import { createEffect } from "effector"; const fx = createEffect((value) => value + 1); fx.done.watch(({ params, result }) => { console.log("Call with argument", params, "completed with value", result); }); await fx(2); // => Call with argument 2 completed with value 3 ``` Run example. *** #### `.doneData` [Derived event][eventTypes] that triggers with the result of successful effect execution. * **Type** ```ts interface Effect { doneData: Event; } ``` * **Examples** ```js import { createEffect } from "effector"; const fx = createEffect((value) => value + 1); fx.doneData.watch((result) => { console.log(`Effect completed successfully, returning ${result}`); }); await fx(2); // => Effect completed successfully, returning 3 ``` Run example. *** #### `.fail` [Derived event][eventTypes] that triggers with the error that occurred during effect execution and the argument passed during the call. * **Type** ```ts interface Effect { fail: Event<{ params: Params; error: Fail }>; } ``` * **Examples** ```js import { createEffect } from "effector"; const fx = createEffect(async (value) => { throw Error(value - 1); }); fx.fail.watch(({ params, error }) => { console.log("Call with argument", params, "failed with error", error.message); }); fx(2); // => Call with argument 2 failed with error 1 ``` Run example. *** #### `.failData` [Derived event][eventTypes] that triggers with the error that occurred during effect execution. * **Type** ```ts interface Effect { failData: Event; } ``` * **Examples** ```js import { createEffect } from "effector"; const fx = createEffect(async (value) => { throw Error(value - 1); }); fx.failData.watch((error) => { console.log(`Call failed with error ${error.message}`); }); fx(2); // => Call failed with error 1 ``` Run example. *** #### `.finally` [Derived event][eventTypes] that triggers on both success and failure of effect completion with detailed information about arguments, results, and execution status. * **Type** ```ts interface Effect { finally: Event< | { status: "done"; params: Params; result: Done; } | { status: "fail"; params: Params; error: Fail; } >; } ``` * **Examples** ```js import { createEffect } from "effector"; const fetchApiFx = createEffect(async ({ time, ok }) => { await new Promise((resolve) => setTimeout(resolve, time)); if (ok) { return `${time} ms`; } throw Error(`${time} ms`); }); fetchApiFx.finally.watch((value) => { switch (value.status) { case "done": console.log("Call with argument", value.params, "completed with value", value.result); break; case "fail": console.log("Call with argument", value.params, "failed with error", value.error.message); break; } }); await fetchApiFx({ time: 100, ok: true }); // => Call with argument {time: 100, ok: true} completed with value 100 ms fetchApiFx({ time: 100, ok: false }); // => Call with argument {time: 100, ok: false} failed with error 100 ms ``` Run example. *** #### `.pending` [Derived store][storeTypes] that shows whether the effect is currently executing. * **Type** ```ts interface Effect { pending: Store; } ``` * **Detailed description** This property eliminates the need to write code like this: ```js const $isRequestPending = createStore(false) .on(requestFx, () => true) .on(requestFx.done, () => false) .on(requestFx.fail, () => false); ``` * **Examples** ```jsx import React from "react"; import { createEffect } from "effector"; import { useUnit } from "effector-react"; const fetchApiFx = createEffect(async (ms) => { await new Promise((resolve) => setTimeout(resolve, ms)); }); fetchApiFx.pending.watch(console.log); // => false const App = () => { const loading = useUnit(fetchApiFx.pending); return
    {loading ? "Loading..." : "Loading complete"}
    ; }; fetchApiFx(1000); // => true // => false ``` Run example. *** #### `.inFlight` [Derived store][storeTypes] that shows the number of running effects that are currently executing. Can be used to limit the number of concurrent requests. * **Type** ```ts interface Effect { inFlight: Store; } ``` * **Detailed description** This property eliminates the need to write code like this: ```js const $requestsInFlight = createStore(0) .on(requestFx, (n) => n + 1) .on(requestFx.done, (n) => n - 1) .on(requestFx.fail, (n) => n - 1); ``` * **Examples** ```js import { createEffect } from "effector"; const fx = createEffect(async () => { await new Promise((resolve) => setTimeout(resolve, 500)); }); fx.inFlight.watch((amount) => { console.log("requests in flight:", amount); }); // => requests in flight: 0 const req1 = fx(); // => requests in flight: 1 const req2 = fx(); // => requests in flight: 2 await Promise.all([req1, req2]); // => requests in flight: 1 // => requests in flight: 0 ``` Run example. *** #### `.sid` Unique unit identifier. It's important to note that SID doesn't change on each application run, it's statically written into your application bundle for absolute unit identification. Set automatically through Babel plugin. * **Type** ```ts interface Effect { sid: string | null; } ``` *** #### `.shortName` String property containing the variable name in which the effect was declared. Effect name. Set either explicitly through the `name` field in createEffect, or automatically through babel plugin. * **Type** ```ts interface Effect { shortName: string; } ``` *** #### `.compositeName` Composite effect name (including domain and short name) — useful for logging and tracing. * **Type** ```ts interface Effect { compositeName: { shortName: string; fullName: string; path: Array; }; } ``` * **Examples** ```ts import { createEffect, createDomain } from "effector"; const first = createEffect(); const domain = createDomain(); const second = domain.createEffect(); console.log(first.compositeName); // { // "shortName": "first", // "fullName": "first", // "path": [ // "first" // ] // } console.log(second.compositeName); // { // "shortName": "second", // "fullName": "domain/second", // "path": [ // "domain", // "second" // ] // } ``` ### Related API and Articles * **API** * createEffect - Creating a new effect * Event API - Description of events, their methods and properties * Store API - Description of stores, their methods and properties * sample - Key operator for building connections between units * attach - Creates new effects based on other effects * **Articles** * Working with effects * How to type effects and other units * Guide to testing effects and other units # Event import Tabs from "@components/Tabs/Tabs.astro"; import TabItem from "@components/Tabs/TabItem.astro"; ## Event API ```ts import { type Event, type EventCallable, createEvent } from "effector"; const event = createEvent(); ``` An event in Effector represents a user action, a step in the application process, a command to execute, an intention to change something, and much more. An event acts as an entry point into the reactive data flow — a simple way to tell the app "something happened." > TIP this is your canonical event: > > If you're not familiar with events and how to work with them, start here: What are events and how to use them. ### Event Types It’s important to understand that there are two types of events: 1. **Events**, created using createEvent or .prepend. These events are of type EventCallable and can be triggered directly or used in the target argument of the sample method. 2. **Derived events**, created using .map, .filter, or .filterMap. These are of type Event and **cannot be triggered or passed into target** — Effector triggers them internally in the correct order. However, you can subscribe to them via sample or watch. ### Event Interface Available methods and properties: |
    Method/Property
    | Description | | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | | prepend(fn) | Creates a new event, transforms the input using `fn`, and passes it to the original event. | | map(fn) | Creates a new derived event triggered with the result of `fn` after the original event is fired. | | filter({fn}) | Creates a new derived event that fires only if `fn` returns `true`. | | filterMap(fn) | Creates a new derived event triggered with `fn` if it's not `undefined`. | | watch(watcher) | Adds a listener called on every event trigger. | | subscribe(observer) | Low-level method to integrate the event with the `Observable` pattern. | | sid | Unique unit identifier. | | shortName | The variable name in which the event is declared. | | compositeName | Full composite name (domain + shortName) — useful for logging and tracing. | ### Event Methods #### `.prepend(fn)` > INFO info: > > This method exists **only** for events that are not derived (`EventCallable`)! > That means it can only be used on events created with createEvent. Creates a new `EventCallable`, which calls `fn` and passes the transformed data to the original event. * **Formula** ```ts const second = first.prepend(fn); ``` * **Type** ```ts event.prepend( fn: (_: Before) => Payload ): EventCallable ``` * **Examples** ```ts import { createEvent } from "effector"; // original event const userPropertyChanged = createEvent(); const changeName = userPropertyChanged.prepend((name) => ({ field: "name", value: name, })); const changeRole = userPropertyChanged.prepend((role) => ({ field: "role", value: role.toUpperCase(), })); userPropertyChanged.watch(({ field, value }) => { console.log(`User property "${field}" changed to ${value}`); }); changeName("john"); // => User property "name" changed to john changeRole("admin"); // => User property "role" changed to ADMIN changeName("alice"); // => User property "name" changed to alice ``` Open example You can treat this method as a wrapper function. Suppose you need to frequently call a function with an inconvenient API: ```ts import { sendAnalytics } from "./analytics"; export function reportClick(item: string) { const argument = { type: "click", container: { items: [arg] } }; return sendAnalytics(argument); } ``` That’s exactly what `.prepend()` does: ```ts import { sendAnalytics } from "./analytics"; export const reportClick = sendAnalytics.prepend((item: string) => { return { type: "click", container: { items: [arg] } }; }); reportClick("example"); // reportClick triggered "example" // sendAnalytics triggered with { type: "click", container: { items: ["example"] } } ``` * **Detailed description** Works like a reversed .map. In `.prepend`, data is transformed **before** the event is triggered. In .map, it’s transformed **after**. If the original event belongs to a domain, the new event will inherit that domain. * **Return value** Returns a new event. *** #### `.map(fn)` > INFO Cleanliness is our everything!: > > The function `fn` **must be pure**. Creates a new **derived event**, which is triggered after the original event, using the result of function `fn` as its argument. * **Formula** ```ts // Works for any event — both regular and derived const first: Event | EventCallable; const second: Event = first.map(fn); ``` * **Type** ```ts event.map(fn: (payload: Payload) => T): Event ``` * **Examples** ```ts import { createEvent } from "effector"; const userUpdated = createEvent<{ name: string; role: string }>(); // You can split data flow with .map() const userNameUpdated = userUpdated.map(({ name }) => name); // Or transform the data const userRoleUpdated = userUpdated.map((user) => user.role.toUpperCase()); userNameUpdated.watch((name) => console.log(`User name is now [${name}]`)); userRoleUpdated.watch((role) => console.log(`User role is now [${role}]`)); userUpdated({ name: "john", role: "admin" }); // => User name is now [john] // => User role is now [ADMIN] ``` Open example * **Detailed description** The `.map` method allows you to split and control the data flow, extract fields, or transform values within your business logic. * **Return value** Returns a new derived event. *** #### `.filter({ fn })` > TIP Tip: > > sample with the `filter` argument is the preferred method for filtering: > > ```ts > const event = createEvent(); > > const filteredEvent = sample({ > clock: event, > filter: () => true, > }); > ``` `.filter` creates a **derived** event, which is triggered **only** if the function `fn` returns `true`. This is helpful for branching the data flow and reacting to specific conditions. * **Formula** ```ts const first: Event | EventCallable; const second: Event = first.filter({ fn }); ``` * **Type** ```ts event.filter(config: { fn(payload: Payload): boolean }): Event ``` * **Examples** ```js import { createEvent, createStore } from "effector"; const numbers = createEvent(); const positiveNumbers = numbers.filter({ fn: ({ x }) => x > 0, }); const $lastPositive = createStore(0); $lastPositive.on(positiveNumbers, (n, { x }) => x); $lastPositive.watch((x) => { console.log("Last positive number:", x); }); // => Last positive number: 0 numbers({ x: 0 }); // no output numbers({ x: -10 }); // no output numbers({ x: 10 }); // => Last positive number: 10 ``` Open example ```js import { createEvent, createStore, sample } from "effector"; const numbers = createEvent(); const positiveNumbers = sample({ clock: numbers, filter: ({ x }) => x > 0, }); const $lastPositive = createStore(0); $lastPositive.on(positiveNumbers, (n, { x }) => x); $lastPositive.watch((x) => { console.log("Last positive number:", x); }); // => Last positive number: 0 numbers({ x: 0 }); // no output numbers({ x: -10 }); // no output numbers({ x: 10 }); // => Last positive number: 10 ``` * **Return value** Returns a new derived event. *** #### `.filterMap(fn)` > TIP Our beloved sample: > > This method can also be replaced with a sample operation using the `filter` + `fn` arguments: > > ```ts > const event = createEvent(); > > const filteredAndMappedEvent = sample({ > clock: event, > filter: () => true, > fn: () => "value", > }); > ``` This method creates a derived event, which **may** be triggered if the result of `fn` is **not undefined**. It combines filtering and mapping in a single step. Ideal for working with JavaScript APIs that sometimes return `undefined`. * **Formula** ```ts const first: Event | EventCallable; const second: Event = first.filterMap(fn); ``` * **Type** ```ts event.filterMap(fn: (payload: Payload) => T | undefined): Event ``` * **Examples** ```tsx import { createEvent } from "effector"; const listReceived = createEvent(); // Array.prototype.find() returns undefined when the element isn't found const effectorFound = listReceived.filterMap((list) => { return list.find((name) => name === "effector"); }); effectorFound.watch((name) => console.info("Found:", name)); listReceived(["redux", "effector", "mobx"]); // => Found: effector listReceived(["redux", "mobx"]); // no output ``` > INFO Attention: > > The function `fn` must return some data. If `undefined` is returned, the derived event call will be skipped. Open example * **Return value** Returns a new derived event. *** #### `.watch(watcher)` The `.watch` method calls the provided `watcher` callback **every time** the event is triggered. > TIP Remember: > > The `watch` method does not handle or report exceptions, does not manage the completion of asynchronous operations, and does not resolve data race conditions. > > Its primary purpose is for short-term debugging and logging. Learn more in the Events section. * **Formula** ```ts const event: Event | EventCallable; const unwatch: () => void = event.watch(fn); ``` * **Type** ```ts event.watch(watcher: (payload: Payload) => any): Subscription ``` * **Examples** ```js import { createEvent } from "effector"; const sayHi = createEvent(); const unwatch = sayHi.watch((name) => console.log(`${name}, hello!`)); sayHi("Peter"); // => Peter, hello! unwatch(); sayHi("Drew"); // => nothing happens ``` Open example * **Return value** Returns a function to cancel the subscription. *** #### `.subscribe(observer)` This is a **low-level** method for integrating events with the standard `Observable` pattern. Further reading: * [RxJS Observables](https://rxjs.dev/guide/observable) * [TC39 proposal for Observables](https://github.com/tc39/proposal-observable) > INFO Remember: > > You don't need to use this method yourself. It's used under the hood by rendering engines and so on. * **Formula** ```ts const event = createEvent(); event.subscribe(observer); ``` * **Type** ```ts event.subscribe(observer: Observer): Subscription ``` * **Examples** ```ts import { createEvent } from "effector"; const userLoggedIn = createEvent(); const subscription = userLoggedIn.subscribe({ next: (login) => { console.log("User login:", login); }, }); userLoggedIn("alice"); // => User login: alice subscription.unsubscribe(); userLoggedIn("bob"); // => nothing happens ``` *** ### Event Properties These properties are mainly set using effector/babel-plugin or @effector/swc-plugin, so they are only available when using Babel or SWC. #### `.sid` A **unique identifier** for each event. SID is **statically recorded** in your application bundle and doesn’t change between app runs. This makes it perfect for identifying units across threads or between client and server. Example: [examples/worker-rpc](https://github.com/effector/effector/tree/master/examples/worker-rpc) * **Type** ```ts interface Event { sid: string | null; } ``` *** #### `.shortName` Contains the **variable name** in which the event was declared. ```ts import { createEvent } from "effector"; const demo = createEvent(); // demo.shortName === 'demo' ``` Reassigning the event to another variable doesn’t change this: ```ts const another = demo; // another.shortName === 'demo' ``` * **Type** ```ts interface Event { shortName: string; } ``` *** #### `.compositeName` Contains the **full path** of the event in your app’s structure. If the event was created inside a domain, its name will reflect that. > TIP TIP: > > Usually, if a long name is required, it's better to pass it explicitly in the `name` field. ```ts import { createEvent, createDomain } from "effector"; const first = createEvent(); const domain = createDomain(); const second = domain.createEvent(); console.log(first.compositeName); // { // shortName: "first", // fullName: "first", // path: ["first"] // } console.log(second.compositeName); // { // shortName: "second", // fullName: "domain/second", // path: ["domain", "second"] // } ``` * **Type** ```ts interface Event { compositeName: { shortName: string; fullName: string; path: Array; }; } ``` ### Event Peculiarities 1. In Effector, every event supports **only one argument**. If you call an event like `someEvent(first, second)`, only the **first argument** will be used — the rest are ignored. 2. Inside event methods, **you must not call other events or effects**. All provided functions must be **pure** — no side effects, no async calls. ### Related APIs and Articles * **API** * createEvent — create a new event * createApi — create a set of events for a store * merge — merge multiple events into one * sample — core operator to connect units * **Articles** * How to work with events * Thinking in Effector and why events matter * TypeScript guide to events and units # Scope API ## Scope API ```ts import { type Scope, fork } from "effector"; const scope = fork(); ``` `Scope` is a fully isolated instance of application. The primary purpose of scope includes SSR (Server-Side Rendering) but is not limited to this use case. A `Scope` contains an independent clone of all units (including connections between them) and basic methods to access them. > TIP scope matters: > > If you want to get deeper about scopes then check out great article about isolated scopes.
    > We also have few related guides: > > * How to fix lost scope > * Using scopes with SSR > * Writing test for units ### Scope peculiarities 1. There are a few rules that must be followed to work successfully with scope. 2. Your scope can be lost to avoid this use . ### Scope methods #### `.getState($store)` Returns the value of a store in a given scope: * **Formula** ```ts const scope: Scope; const $value: Store | StoreWritable; const value: T = scope.getState($value); ``` * **Type** ```ts scope.getState(store: Store): T; ``` * **Returns** The value of the store. * **Examples** Create two instances of an application, trigger events in them, and test the `$counter` store value in both instances: ```js import { createStore, createEvent, fork, allSettled } from "effector"; const inc = createEvent(); const dec = createEvent(); const $counter = createStore(0); $counter.on(inc, (value) => value + 1); $counter.on(dec, (value) => value - 1); const scopeA = fork(); const scopeB = fork(); await allSettled(inc, { scope: scopeA }); await allSettled(dec, { scope: scopeB }); console.log($counter.getState()); // => 0 console.log(scopeA.getState($counter)); // => 1 console.log(scopeB.getState($counter)); // => -1 ``` Try it. ### Related API and Articles * **API** * scopeBind – Method for binding a unit to a scope * fork – Operator for creating a scope * allSettled – Method for running a unit in a given scope and waiting for the entire chain of effects to complete * serialize – Method for obtaining serialized store values * hydrate – Method for hydrating serialized data * **Articles** * How to lose scope and fix it * Using scopes with SSR * How to test units # Store API ## Store API ```ts import { type Store, type StoreWritable, createStore } from "effector"; const $store = createStore(); ``` A *Store* is an object that holds the state value. The store updates when the new value is not strictly equal (`!==`) to the current one and is not `undefined` (unless the store is configured with `skipVoid: false`). A store is a Unit. Some stores can be derived. > TIP What is a store anyway?: > > If you're not yet familiar with how to work with a store, feel free to start here. ### Store Interface Available store methods and properties: | Method/Property | Description | | ----------------------------------------------------- | ------------------------------------------------------------ | | map(fn) | Creates a new derived store | | on(trigger, reducer) | Updates state via a `reducer` when the `trigger` is fired | | watch(watcher) | Calls the `watcher` function every time the store is updated | | reset(...triggers) | Resets the store to its initial state | | off(trigger) | Removes the subscription to the specified trigger | | updates() | Event that fires when the store updates | | reinit() | Event to reinitialize the store | | shortName | ID or short name of the store | | defaultState | Initial state of the store | | getState() | Returns the current state | ### Immutability A store in effector is immutable. This means that updates will only occur if the handler function (such as `combine`, `sample`, or `on`) returns a new object. For example, before using array methods, you need to create a new reference to it. Here’s how to do it correctly: ```ts $items.on(addItem, (items, newItem) => { const updatedItems = [...items]; // ✅ .push method is called on a new array updatedItems.push(newItem); return updatedItems; }); ``` This approach should not be used, as the store **will not be updated**: ```ts $items.on(addItem, (items, newItem) => { // ❌ Error! The array reference remains the same, the store will not be updated items.push(newItem); return items; }); ``` Updating objects works in a similar way. A store in effector should be as small as possible, responsible for a specific part of the business logic, unlike, for example, Redux, whose store tends to hold everything together. When the state is atomic, the need for spreading objects becomes less frequent. However, if there is a need to frequently update deeply nested data, it is acceptable to use [immer](https://immerjs.github.io/immer/produce) to simplify repetitive code when updating the state. ### Store Methods #### `.map(fn)` Accepts a function `fn` and returns a derived store that automatically updates when the original store changes. > TIP Recommendation: > > For creating derived stores, prefer using combine($store, fn) over `.map()` for better composability and consistency with other combine forms. * **Formulae** ```ts $source.map(fn, config?); ``` * **Type** ```ts const $derived = $source.map( fn: (value: SourceValue) => T, config?: { skipVoid?: boolean } ): Store ``` * **Examples** Basic usage: ```ts import { createEvent, createStore } from "effector"; const changed = createEvent(); const $title = createStore(""); const $titleLength = $title.map((title) => title.length); $title.on(changed, (_, newTitle) => newTitle); $titleLength.watch((length) => { console.log("new length", length); }); changed("hello"); changed("world"); changed("hello world"); ``` Try it You can pass a config object with `skipVoid: false` to allow the store to accept `undefined`: ```js const $titleLength = $title.map((title) => title.length, { skipVoid: false }); ``` * **Detailed Description** The `map` method runs the function `fn` with the current store state as input every time the original store updates. The return value becomes the new state of the derived store. * **Returns** Returns a new derived store. #### `.on(trigger, reducer)` Updates state using a reducer when the `trigger` is fired. * **Formulae** ```ts $store.on(trigger, reducer); ``` * **Type** ```ts $store.on( trigger: Unit | Unit[] reducer: (state: State, payload: T) => State | void ): this ``` * **Examples** ```ts import { createEvent, createStore } from "effector"; const $counter = createStore(0); const incrementedBy = createEvent(); $counter.on(incrementedBy, (value, incrementor) => value + incrementor); $counter.watch((value) => { console.log("updated", value); }); incrementedBy(2); incrementedBy(2); ``` Try it * **Returns** Returns the current store. #### `.watch(watcher)` Calls the `watcher` function whenever the store updates. * **Formulae** ```ts const unwatch = $store.watch(watcher); ``` * **Type** ```ts $store.watch(watcher: (state: State) => any): Subscription ``` * **Examples** ```ts import { createEvent, createStore } from "effector"; const add = createEvent(); const $store = createStore(0); $store.on(add, (state, payload) => state + payload); $store.watch((value) => console.log(`current value: ${value}`)); add(4); add(3); ``` Try it * **Returns** Returns a subscription cancellation function. #### `.reset(...triggers)` Resets the store to its default value when any of the `triggers` fire. * **Formulae** ```ts $store.reset(...triggers); ``` * **Type** ```ts $store.reset(...triggers: Array>): this ``` * **Examples** ```ts import { createEvent, createStore } from "effector"; const increment = createEvent(); const reset = createEvent(); const $store = createStore(0) .on(increment, (state) => state + 1) .reset(reset); $store.watch((state) => console.log("changed", state)); increment(); increment(); reset(); ``` Try it * **Returns** Returns the current store. #### `.off(trigger)` Removes the reducer for the specified `trigger`. * **Formulae** ```ts $store.off(trigger); ``` * **Type** ```ts $store.off(trigger: Unit): this ``` * **Examples** ```ts import { createEvent, createStore, merge } from "effector"; const changedA = createEvent(); const changedB = createEvent(); const $store = createStore(0); const changed = merge([changedA, changedB]); $store.on(changed, (state, params) => state + params); $store.off(changed); ``` Try it * **Returns** Returns the current store. ### Store Properties #### `.updates` An event that fires on every store update. * **Examples** ```ts import { createStore, is } from "effector"; const $clicksAmount = createStore(0); is.event($clicksAmount.updates); // true $clicksAmount.updates.watch((amount) => { console.log(amount); }); ``` Try it * **Returns** A derived event representing the store's updates. #### `.reinit` Event to reinitialize the store to its default state. * **Examples** ```ts import { createStore, createEvent, sample, is } from "effector"; const $counter = createStore(0); is.event($counter.reinit); const increment = createEvent(); $counter.reinit(); console.log($counter.getState()); ``` Try it * **Returns** An event that reinitializes the store. #### `.shortName` A string property containing the store's ID or short name. * **Examples** ```ts const $store = createStore(0, { name: "someName", }); console.log($store.shortName); // someName ``` Try it * **Returns** The store’s ID or short name. #### `.defaultState` The store’s default state value. * **Example** ```ts const $store = createStore("DEFAULT"); console.log($store.defaultState === "DEFAULT"); // true ``` * **Returns** The default state value. ### Utility Methods #### `.getState()` Returns the current state of the store. > WARNING Caution!: > > Using `getState()` in business logic is not recommended — it's better to pass data through `sample`. * **Examples** ```ts import { createEvent, createStore } from "effector"; const add = createEvent(); const $number = createStore(0).on(add, (state, data) => state + data); add(2); add(3); console.log($number.getState()); ``` Try it * **Returns** The current state of the store. ### Related APIs * createStore – Creates a new store * combine – Combines multiple stores into a derived store * sample – A core operator for connecting units * createEvent – Creates an event * createEffect – Creates an effect # allSettled ## Methods ### `allSettled(unit, {scope, params?})` Calls the provided unit within the current scope and wait for all triggered effects to complete. #### Formulae ```ts allSettled(unit: Event, {scope: Scope, params?: T}): Promise allSettled(unit: Effect, {scope: Scope, params?: T}): Promise< | {status: 'done'; value: Done} | {status: 'fail'; value: Fail} > allSettled(unit: Store, {scope: Scope, params?: T}): Promise ``` #### Arguments 1. `unit`: or to be called 2. `scope`: 3. `params`: params passed to `unit` > INFO since: > > Return value for effect is supported since [effector 21.4.0](https://changelog.effector.dev/#effector-21-4-0) #### Examples > TIP Contribution: > > TBD > > Please, [open PullRequest](https://github.com/effector/effector) and contribute examples for this section via "Edit this page" link below. ### `allSettled(scope)` Checks the provided scope for any ongoing computations and wait for their completion. #### Formulae ```ts allSettled(scope): Promise ``` #### Arguments 1. `scope`: > INFO since: > > Supported since effector 22.5.0 #### Examples ##### Usage in tests For example, tests that validate the integration with an external reactive API ```ts import {createEvent, sample, fork, scopeBind, allSettled} from 'effector' test('integration with externalSource', async () => { const scope = fork() const updated = createEvent() sample({ clock: updated, target: someOtherLogicStart, }) // 1. Subscribe event to external source const externalUpdated = scopeBind(updated, {scope}) externalSource.listen(() => externalUpdates()) // 2. Trigger update of external source externalSource.trigger() // 3. Wait for all triggered computations in effector's scope, even though these were not triggered by effector itself await allSettled(scope) // 4. Check anything as usual expect(...).toBe(...) }) ``` # attach API import Tabs from "@components/Tabs/Tabs.astro"; import TabItem from "@components/Tabs/TabItem.astro"; [effectApi]: /en/api/effector/Effect [storeApi]: /en/api/effector/Store ## `attach` API ```ts import { attach } from "effector"; ``` The `attach` method allows creating new [effects][effectApi] based on existing ones with the ability to access data from [stores][storeApi]. This method enables reusing effect logic with different parameters and automatically passing data from stores. > INFO Properties of attached effects: > > An attached effect is a full-fledged effect that has its own `.done`, `.fail`, and other properties. They trigger only when the attached effect is called, not the original one. > > ```ts > const originalFx = createEffect(async (x: number) => x * 2); > const attachedFx = attach({ effect: originalFx }); > > originalFx.done.watch(() => console.log("original done")); > attachedFx.done.watch(() => console.log("attached done")); > > await attachedFx(5); > // original done > // attached done > > await originalFx(5); > // original done > // (attached done won't trigger) > ``` ### Algorithm 1. You call the effect returned by `attach`, passing your parameters. 2. If `source` is specified, Effector takes the current value from this store. 3. If `mapParams` is specified, this function is called with your parameters and (if present) data from `source`. 4. The result of the `mapParams` function is passed to the original `effect`. 5. The result of executing the original effect is returned. ### `attach` Configuration Forms |
    Form
    | Description | | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | attach({ effect }) | Creates a local copy of the effect with the same behavior. | | attach({ source, effect }) | Creates an effect that automatically passes data from `source` to the original effect when called. | | attach({ mapParams, effect }) | Creates an effect with input parameter transformation via `mapParams` function before passing to the original effect. | | attach({ source, mapParams, effect }) | Creates an effect that combines data from `source` with input parameters via `mapParams` and passes the result to the original effect. | ### Configurations #### `attach({ effect })` Creates a new attached [effect][effectApi] that will call `effect` with the passed parameters as is. This allows creating separate effects with shared behavior. * **Formula** ```ts const attachedFx = attach({ effect, // original effect whose behavior we're copying name? // name of the new effect }); ``` * **Type** ```ts export function attach>(config: { effect: FX; name?: string; }): Effect, EffectResult, EffectError>; ``` * **Examples** This allows creating a *local* copy of an effect to react only to calls from the current *local* code. ```ts // common effect for the entire application const sendAnalyticsFx = createEffect(async (event: { name: string; data: any }) => { console.log("Analytics:", event.name); }); // in auth module - local copy const trackAuthFx = attach({ effect: sendAnalyticsFx, name: "trackAuthFx", }); // handle only events from auth module trackAuthFx.done.watch(() => { console.log("Auth event tracked"); }); // in cart module - another local copy const trackCartFx = attach({ effect: sendAnalyticsFx, name: "trackCartFx", }); // handle only events from cart module trackCartFx.done.watch(() => { console.log("Cart event tracked"); }); trackAuthFx({ name: "login", data: {} }); // Analytics: login // Auth event tracked trackCartFx({ name: "add_to_cart", data: {} }); // Analytics: add_to_cart // Cart event tracked ``` Run example. #### `attach({ source, effect })` Creates a new attached [effect][effectApi] that when called will run the original `effect` or handler with data from `source`. * **Formula** ```ts const attachedFx = attach({ source, // data source passed to the effect effect, // original effect whose behavior we're copying name? // name of the new effect domain? // domain, in case effect is a handler }); ``` * **Type** ```ts export function attach< States extends StoreShape, FX extends | Effect, any, any> | ((state: GetShapeValue, ...params: any[]) => any), >(config: { source: States; effect: FX; name?: string; domain?: Domain; }): Effect, EffectError>; ``` * **Features** * The `effect` argument can be either an [effect][effectApi] or a regular handler. * The `source` argument is not reactive - store changes don't automatically trigger effect execution. * `source` can be a store, an object of stores, or an array of stores. * The `domain` parameter can only be passed if `effect` is a handler.

    * **Examples** Simple usage with one store and handler:

    ```ts // effect for loading data const loadDataFx = createEffect(async (id: number) => { return fetch(`/api/data/${id}`).then((res) => res.json()); }); // store with current id const $currentId = createStore(1); // effect with store binding const loadCurrentDataFx = attach({ source: $currentId, effect: async (id: number) => { const res = await fetch(`/api/data/${id}`); return await res.json(); }, }); ```
    `source` argument as object:

    ```ts import { createEffect, createStore, attach } from "effector"; const requestPageFx = createEffect<{ page: number; size: number }, number>( 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 ```
    Usage with array of stores:

    ```ts const $lat = createStore(55.7558); const $lon = createStore(37.6173); // weather fetching effect const fetchWeatherFx = createEffect(([lat, lon]: [number, number]) => fetch(`/api/weather?lat=${lat}&lon=${lon}`).then((res) => res.json()), ); // combining array of stores const loadWeatherFx = attach({ source: combine([$lat, $lon]), effect: fetchWeatherFx, }); ```
    * **Return value** Returns a new [effect][effectApi]. #### `attach({ mapParams, effect })` Creates a new attached [effect][effectApi] that when called will run the original `effect`, transforming parameters with the `mapParams` function. * **Formula** ```ts const attachedFx = attach({ effect, // original effect whose behavior we're copying mapParams, // function for data transformation name? // name of the new effect }); ``` * **Type** ```ts export function attach>(config: { effect: FX; mapParams: (params: Params) => EffectParams; name?: string; }): Effect, EffectError>; ``` * **Features** * If `mapParams` fails with an error, the attached effect will immediately complete its execution with error, and the original effect won't be called. * `mapParams` must return the same type that `originalFx` accepts as parameters. In case of error, you need to control error type compatibility with the effect yourself, TypeScript won't help here. ```ts const attachedFx: Effect = attach({ effect: originalFx, mapParams: (): A { throw new AnyNonFailType(); // this might be incompatible with type `Fail`. }, }); ``` * **Examples** Using `mapParams` to transform arguments: ```ts 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 } ``` Run example As well as handling exceptions: ```ts 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 ``` Run example * **Return value** Returns a new [effect][effectApi]. #### `attach({ source, mapParams, effect })` Creates a new attached [effect][effectApi] that will read values from the `source` store, pass them with parameters to the `mapParams` function, and then call `effect` with the result. * **Formula** ```ts const attachedFx = attach({ source, // data source passed to the effect mapParams, // function for data transformation effect, // original effect whose behavior we're copying name? // name of the new effect }); ``` * **Type** ```ts export function attach< States extends StoreShape, FX extends Effect, FN extends (params: any, source: GetShapeValue) => EffectParams, >(config: { source: States; effect: FX; mapParams: FN; name?: string; }): Effect[0], EffectResult, EffectError>; ``` * **Features** * If `mapParams` fails with an error, the attached effect will immediately complete its execution with error, and the original effect won't be called. * The `mapParams` function must return the same type that the effect in the `effect` argument accepts as parameters. In case of error, you need to control error type compatibility with the effect yourself, TypeScript won't help here. * The `source` argument is not reactive - store changes don't automatically trigger effect execution. * `source` can be a store, an object of stores, or an array of stores. * **Examples** ```ts import { createStore, createEvent, createEffect, attach, sample } from "effector"; const $credentials = createStore({ username: "", password: "" }); const $authToken = createStore(""); const apiFx = createEffect(async ({ url, data, token }) => { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Authorization: token ? `Bearer ${token}` : "", }, body: JSON.stringify(data), }); return response.json(); }); const loginFx = attach({ source: { creds: $credentials, token: $authToken }, mapParams: (_, { creds, token }) => ({ url: "/api/login", data: creds, token, }), effect: apiFx, }); ``` * **Return value** Returns a new [effect][effectApi]. ### Related API and Articles * **API** * Effect API - Description of effects, their methods and properties * createEffect - Creating a new effect * sample - Key operator for building connections between units * Store API - Description of stores, their methods and properties * **Articles** * Working with effects * Typing # Babel plugin Built-in plugin for babel can be used for ssr and debugging. It inserts a name a Unit, inferred from variable name and `sid` (Stable IDentifier), computed from the location in the source code. For example, in case effects without handlers, it improves error messages by clearly showing in which effect error happened. ```js import { createEffect } from "effector"; const fetchFx = createEffect(); fetchFx(); // => no handler used in fetchFx ``` Try it ## Usage In the simplest case, it can be used without any configuration: ```json // .babelrc { "plugins": ["effector/babel-plugin"] } ``` ## SID > INFO since: > > [effector 20.2.0](https://changelog.effector.dev/#effector-20-2-0) Stable hash identifier for events, effects, stores and domains, preserved between environments, to handle client-server interaction within the same codebase. The crucial value of sid is that it can be autogenerated by `effector/babel-plugin` with default config, and it will be stable between builds. > TIP Deep dive explanation: > > If you need the detailed deep-dive explanation about why we need SIDs and how they are used internally, you can find it by following this link See [example project](https://github.com/effector/effector/tree/master/examples/worker-rpc) ```js // common.js import { createEffect } from "effector"; export const getUser = createEffect({ sid: "GET /user" }); console.log(getUsers.sid); // => GET /user ``` ```js // worker.js import { getUsers } from "./common.js"; getUsers.use((userID) => fetch(userID)); getUsers.done.watch(({ result }) => { postMessage({ sid: getUsers.sid, result }); }); onmessage = async ({ data }) => { if (data.sid !== getUsers.sid) return; getUsers(data.userID); }; ``` ```js // client.js import { createEvent } from "effector"; import { getUsers } from "./common.js"; const onMessage = createEvent(); const worker = new Worker("worker.js"); worker.onmessage = onMessage; getUsers.use( (userID) => new Promise((rs) => { worker.postMessage({ sid: getUsers.sid, userID }); const unwatch = onMessage.watch(({ data }) => { if (data.sid !== getUsers.sid) return; unwatch(); rs(data.result); }); }), ); ``` ## Configuration ### `hmr` > INFO since: > > [effector 23.4.0](https://changelog.effector.dev/#effector-23.4.0) Enable Hot Module Replacement (HMR) support to clean up links, subscriptions and side effects managed by Effector. This prevents double-firing of Effects and watchers. > WARNING Interaction with factories: > > HMR support show best results when all factories in project are properly declared, which help plugin and runtime to know which code to clear on hot updates #### Formulae ```json "effector/babel-plugin", { "hmr": "es" } ] ``` * Type: `boolean` | `"es"` | `"cjs"` * `true`: Use hmr with auto-detection of target case. Based on [supportsStaticESM](https://babeljs.io/docs/options#caller) babel feature with wide support in bundlers * `"es"`: Use `import.meta.hot` HMR API in bundlers that are ESM-compliant, like Vite and Rollup * `"cjs"`: Use `module.hot` HMR API in bundlers that rely on CommonJS modules, like Webpack, Next.js or React Native * `false`: Disable Hot Module Replacement * Default: `false` > INFO In Production: > > When bundling for production, make sure to set the `hmr` option to `false` or remove it to reduce bundle size and improve runtime performance. ### `forceScope` > INFO since: > > [effector 23.4.0](https://changelog.effector.dev/#effector-23.4.0) Adds `forceScope` to all hooks from `effector-react`. This prevents mistakes when events called in non-scoped environment. #### Formulae ```json "effector/babel-plugin", { "forceScope": true } ``` * Type: `boolean` * `true`: Adds `{ forceScope: true }` to hooks like `useUnit` * `false`: Do nothing * Default: `false` ### `importName` Specifying import name or names to process by plugin. Import should be used in the code as specified. #### Formulae ```json [ "effector/babel-plugin", { "importName": ["effector"] } ] ``` * Type: `string | string[]` * Default: `['effector', 'effector/compat']` ### `factories` Accepts an array of module names which exports treat as custom factories, therefore, each function call provides a unique prefix for sids of units inside them. Used to SSR(Server Side Rendering) and it's not required for client-only application. > INFO since: > > [effector 21.6.0](https://changelog.effector.dev/#effector-21-6-0) #### Formulae ```json [ "effector/babel-plugin", { "factories": ["path/here"] } ] ``` * Type: `string[]` * Factories can have any number of arguments. * Factories can create any number of units. * Factories can call any effector methods. * Factories can call other factories from other modules. * Modules with factories can export any number of functions. * Factories should be compiled with `effector/babel-plugin` as well as code which use them. #### Examples ```json // .babelrc { "plugins": [ [ "effector/babel-plugin", { "factories": ["src/createEffectStatus", "~/createCommonPending"] } ] ] } ``` ```js // ./src/createEffectStatus.js import { rootDomain } from "./rootDomain"; export function createEffectStatus(fx) { const $status = rootDomain.createStore("init").on(fx.finally, (_, { status }) => status); return $status; } ``` ```js // ./src/statuses.js import { createEffectStatus } from "./createEffectStatus"; import { fetchUserFx, fetchFriendsFx } from "./api"; export const $fetchUserStatus = createEffectStatus(fetchUserFx); export const $fetchFriendsStatus = createEffectStatus(fetchFriendsFx); ``` Import `createEffectStatus` from `'./createEffectStatus'` was treated as factory function, so each store created by it has its own sid and will be handled by serialize independently, although without `factories` they will share the same `sid`. ### `reactSsr` Replaces imports from `effector-react` to `effector-react/scope`. Useful for building both server-side and client-side builds from the same codebase. > WARNING Deprecated: > > Since [effector 23.0.0](https://changelog.effector.dev/#effector-23-0-0) the core team recommends deleting this option from `babel-plugin` configuration because effector-react supports SSR by default. #### Formulae ```json [ "effector/babel-plugin", { "reactSsr": false } ] ``` * Type: `boolean` * Default: `false` ### `addNames` Adds name to units factories call. Useful for minification and obfuscation of production builds. > INFO since: > > [effector 21.8.0](https://changelog.effector.dev/#effector-21-8-0) #### Formulae ```json [ "effector/babel-plugin", { "addNames": true } ] ``` * Type: `boolean` * Default: `true` ### `addLoc` Adds location to methods' calls. Used by devtools, for example [effector-logger](https://github.com/effector/logger). #### Formulae ```json [ "effector/babel-plugin", { "addLoc": false } ] ``` * Type: `boolean` * Default: `false` ### `debugSids` Adds a file path and variable name of a unit definition to a sid. Useful for debugging SSR. #### Formulae ```json [ "effector/babel-plugin", { "debugSids": false } ] ``` * Type: `boolean` * Default: `false` ### `transformLegacyDomainMethods` Allows disabling transforming Unit creators on Domain. This option is useful when these transforms interfere with other libraries or your code. The `effector/babel-plugin` may misidentify calls to unit creators because it is hard to know which variables are indeed Domains. If your project can't run due to this, you can turn these transforms off with this flag and pass `Domain` as an argument to regular unit creators, which is a better and more stable alternative. > WARNING: > > Disabling this option will prevent units created with `Domain` methods from having a `sid` and other information. If your code relies on these methods, this will cause issues with your existing code. #### Formulae ```json [ "effector/babel-plugin", { "transformLegacyDomainMethods": false } ] ``` * Type: `boolean` * Default: `true` ### `noDefaults` Option for `effector/babel-plugin` for making custom unit factories with clean configuration. > INFO since: > > [effector 20.2.0](https://changelog.effector.dev/#effector-20-2-0) #### Formulae ```json [ "effector/babel-plugin", { "noDefaults": false } ] ``` * Type: `boolean` * Default: `false` #### Examples ```json // .babelrc { "plugins": [ ["effector/babel-plugin", { "addLoc": true }], [ "effector/babel-plugin", { "importName": "@lib/createInputField", "storeCreators": ["createInputField"], "noDefaults": true }, "createInputField" ] ] } ``` ```js // @lib/createInputField.js import { createStore } from "effector"; import { resetForm } from "./form"; export function createInputField(defaultState, { sid, name }) { return createStore(defaultState, { sid, name }).reset(resetForm); } ``` ```js // src/state.js import { createInputField } from "@lib/createInputField"; const foo = createInputField("-"); /* will be treated as store creator and compiled to const foo = createInputField('-', { name: 'foo', sid: 'z&si65' }) */ ``` ## Usage with Bundlers ### Vite + React (SSR) To use with `effector/babel-plugin`, you have to following next steps: 1. Install `@vitejs/plugin-react` package. 2. `vite.config.js` should be follows: > Note: `effector/babel-plugin` is not a package, it is bundled with `effector` ```js // vite.config.js import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [ react({ babel: { plugins: ["effector/babel-plugin"], // Use .babelrc files babelrc: true, // Use babel.config.js files configFile: true, }, }), ], }); ``` # clearNode ```ts import { clearNode } from "effector"; ``` Method for destroying stores, events, effects, subscriptions, and domains. ## Methods ### `clearNode(unit, config?)` #### Formulae ```ts clearNode(unit, config?: {deep?: boolean}): void ``` #### Arguments 1. `unit` (////): unit to be erased. 2. `config: {}` (optional): config object. * `deep?: boolean` (optional): erase node *and* all of its computed values. #### Returns `void` #### Examples ##### Simple ```js import { createStore, createEvent, clearNode } from "effector"; const inc = createEvent(); const $store = createStore(0).on(inc, (x) => x + 1); inc.watch(() => console.log("inc called")); $store.watch((x) => console.log("store state: ", x)); // => store state: 0 inc(); // => inc called // => store state: 1 clearNode($store); inc(); // => inc called ``` Try it ##### Deep clear ```js import { createStore, createEvent, clearNode } from "effector"; const inc = createEvent(); const trigger = inc.prepend(() => {}); const $store = createStore(0).on(inc, (x) => x + 1); trigger.watch(() => console.log("trigger called")); inc.watch(() => console.log("inc called")); $store.watch((x) => console.log("store state: ", x)); // => store state: 0 trigger(); // => trigger called // => inc called // => store state: 1 clearNode(trigger, { deep: true }); trigger(); // no reaction inc(); // no reaction! // all units, which depend on trigger, are erased // including inc and store, because it depends on inc ``` Try it # combine API [storeApi]: /en/api/effector/Store ## `combine` API ```ts import { combine } from "effector"; ``` The `combine` method allows retrieving the state from each passed [store][storeApi] and combining them into a single value, storing it in a new derived store. The resulting store will update every time any of the passed stores is updated. ### Algorithm 1. `combine` reads the current state from all passed stores. 2. If a transformation function `fn` is provided, it calls `fn` with the values from stores. 3. The result is saved to a new derived store. 4. The derived store updates whenever any of the source stores changes. ### Peculiarities * **Batching updates**: When multiple stores update simultaneously (during one tick), `combine` processes them all at once, resulting in a single update of the derived store. * **Mixing stores with primitives**: You can pass not only stores but also primitives and objects to `combine`. Effector will not track mutations of these primitives and objects - they are treated as static values. * **Pure transformation functions**: All transformation functions passed to `combine` must be pure. * **Strict equality check**: If a transformation function returns the same value as previous (by `!==` comparison), the derived store won't update. * **Error handling**: If a transformation function throws an error during execution, the application will crash. This will be fixed in [effector v24](https://github.com/effector/effector/issues/1163). ### Configuration forms |
    Form
    | Description | | ----------------------------------------------------------- | ----------------------------------------------------------------------- | | combine($a, fn) | Transforms a single store's value via `fn`. | | combine(...$stores, fn?) | Combines multiple stores/values, optionally transforming via `fn`. | | combine({ a: $a, b: $b }, fn?) | Combines stores into an object store, optionally transforming via `fn`. | | combine(\[$a, $b], fn?) | Combines stores into an array store, optionally transforming via `fn`. | ### Configurations #### `combine($store, fn)` Creates a new derived store by transforming a single store's value. This is the **preferred method** for creating derived stores from a single source. It is an alternative way to Store.map(). * **Formula** ```ts const $result = combine($source, (value) => { // transformation logic return result; }); ``` * **Type** ```ts export function combine( a: Store
    , fn: (a: A) => R, config?: { skipVoid?: boolean; }, ): Store; ``` * **Features** * The transformation function `fn` must be pure. * If `fn` returns the same value as previous (by `!==` comparison), the store won't update. * If `fn` throws an error during execution, the application will crash (this will be fixed in [effector v24](https://github.com/effector/effector/issues/1163)). * Optional `config` parameter: see createStore for details on `skipVoid`. * **Examples** ```ts import { createStore, combine } from "effector"; const $name = createStore("John"); const $greeting = combine($name, (name) => `Hello, ${name}!`); $greeting.watch((greeting) => console.log(greeting)); // => Hello, John! ``` ```ts import { createStore, combine } from "effector"; const $price = createStore(100); const $priceWithTax = combine($price, (price) => price * 1.2); $priceWithTax.watch((price) => console.log("Price with tax:", price)); // => Price with tax: 120 ``` * **Return value** Returns a new derived store. *** #### `combine(...$stores, fn?)` Creates a new derived store that combines any number of stores and values. Accepts any number of arguments - stores, primitives, or objects. The last argument can optionally be a transformation function. Without `fn`, wraps all values into an array. With `fn`, transforms values using the function where stores are passed as separate arguments. * **Formula** ```ts // without transformation function const $result = combine($a); const $result = combine($a, $b, $c); // with transformation function const $result = combine($a, (value) => { // transformation logic return result; }); const $result = combine($a, $b, $c, (a, b, c) => { // transformation logic return result; }); ``` * **Type** ```ts export function combine( ...stores: T ): Store<{ [K in keyof T]: T[K] extends Store ? U : T[K] }>; export function combine( ...stores: T, fn: (...stores: { [K in keyof T]: T[K] extends Store ? U : T[K] }) => R, config?: { skipVoid?: boolean }, ): Store; // also supports any number of stores with optional fn as last argument ``` * **Features** * Accepts any number of stores, primitives, or objects as arguments. * Can mix stores with values such as primitives or objects. * Without `fn`, returns an array store with values in the same order as arguments. * When `fn` is provided: * The transformation function `fn` must be pure. * If `fn` returns the same value as previous (by `!==` comparison), the store won't update. * If `fn` throws an error during execution, the application will crash (this will be fixed in [effector v24](https://github.com/effector/effector/issues/1163)). * Optional `config` parameter: see createStore for details on `skipVoid`. * **Examples** Without transformation function - creates an array store: ```ts import { createStore, combine } from "effector"; const $firstName = createStore("John"); const $lastName = createStore("Doe"); const $age = createStore(30); const $userData = combine($firstName, $lastName, $age); $userData.watch((data) => console.log(data)); // => ["John", "Doe", 30] ``` With transformation function - transforms combined values: ```ts import { createStore, combine } from "effector"; const $firstName = createStore("John"); const $lastName = createStore("Doe"); const $fullName = combine($firstName, $lastName, (first, last) => { return `${first} ${last}`; }); $fullName.watch((name) => console.log(name)); // => "John Doe" ``` Combining multiple stores with transformation: ```ts import { createStore, combine } from "effector"; const $price = createStore(100); const $quantity = createStore(2); const $discount = createStore(0.1); const $total = combine($price, $quantity, $discount, (price, qty, disc) => { const subtotal = price * qty; return subtotal - subtotal * disc; }); $total.watch((total) => console.log(`Total: $${total}`)); // => Total: $180 ``` Mixing stores with primitives: ```ts import { createStore, combine } from "effector"; const $userName = createStore("Alice"); const API_URL = "https://api.example.com"; const $userEndpoint = combine($userName, API_URL, (name, url) => { return `${url}/users/${name}`; }); $userEndpoint.watch((endpoint) => console.log(endpoint)); // => https://api.example.com/users/Alice ``` * **Return value** Returns a new derived store. *** #### `combine({ a: $a, b: $b }, fn?)` Creates a new derived store that combines an object of stores. Without `fn`, it creates an object store with values from the passed stores. With `fn`, it transforms the combined values using the function. * **Formula** ```ts // without transformation function const $result = combine({ a: $a, b: $b, // ... more stores }); // with transformation function const $result = combine({ a: $a, b: $b, c: $c }, ({ a, b, c }) => { // transformation logic return result; }); ``` * **Type** ```ts export function combine( shape: State, fn?: (shape: { [K in keyof State]: State[K] extends Store ? U : State[K] }) => R, config?: { skipVoid?: boolean; }, ): Store; ``` * **Features** * Updates are batched when multiple stores change simultaneously. * Can mix stores with values such as primitives or objects. * if `fn` is provided: * The transformation function `fn` must be pure. * If `fn` returns the same value as previous (by `!==` comparison), the store won't update. * If `fn` throws an error during execution, the application will crash (this will be fixed in [effector v24](https://github.com/effector/effector/issues/1163)). * Optional `config` parameter: see createStore for details on `skipVoid`. * **Examples** Without transformation function - creates an object store: ```ts import { createStore, combine } from "effector"; const $firstName = createStore("John"); const $lastName = createStore("Doe"); const $age = createStore(30); const $user = combine({ firstName: $firstName, lastName: $lastName, age: $age, }); $user.watch((user) => console.log(user)); // => { firstName: "John", lastName: "Doe", age: 30 } ``` With transformation function - transforms object values: ```ts import { createStore, combine } from "effector"; const $firstName = createStore("John"); const $lastName = createStore("Doe"); const $age = createStore(30); const $userSummary = combine( { firstName: $firstName, lastName: $lastName, age: $age }, ({ firstName, lastName, age }) => { return `${firstName} ${lastName}, ${age} years old`; }, ); $userSummary.watch((summary) => console.log(summary)); // => "John Doe, 30 years old" ``` Practical example - form validation: ```ts import { createStore, combine } from "effector"; const $email = createStore(""); const $password = createStore(""); const $confirmPassword = createStore(""); const $formValidation = combine( { email: $email, password: $password, confirmPassword: $confirmPassword }, ({ email, password, confirmPassword }) => { const errors = []; if (!email.includes("@")) { errors.push("Invalid email"); } if (password.length < 8) { errors.push("Password must be at least 8 characters"); } if (password !== confirmPassword) { errors.push("Passwords don't match"); } return { isValid: errors.length === 0, errors, }; }, ); ``` Mixing stores with primitives and objects: ```ts import { createStore, combine } from "effector"; const $userId = createStore(123); const $isActive = createStore(true); const $userData = combine({ id: $userId, isActive: $isActive, role: "user", permissions: ["read", "write"], }); $userData.watch((data) => console.log(data)); // => { id: 123, isActive: true, role: "user", permissions: ["read", "write"] } ``` * **Return value** Returns a new derived store. *** #### `combine([$a, $b], fn?)` Creates a new derived store that combines an array of stores. Without `fn`, it creates an array store with values from the passed stores in the same order. With `fn`, it transforms the combined values using the function. * **Formula** ```ts // without transformation function const $result = combine([$a, $b, $c]); // with transformation function const $result = combine([$a, $b, $c], ([a, b, c]) => { // transformation logic return result; }); ``` * **Type** ```ts export function combine( tuple: State, ): Store<{ [K in keyof State]: State[K] extends Store ? U : State[K] }>; export function combine( tuple: State, fn: (tuple: { [K in keyof State]: State[K] extends Store ? U : State[K] }) => R, config?: { skipVoid?: boolean }, ): Store; ``` * **Features** * Array order matches the order of passed stores. * Updates are batched when multiple stores change simultaneously. * Can mix stores with non-store values such as primitives or objects. * When `fn` is provided: * The transformation function `fn` must be pure. * Function receives an array where order matches the input array order. * If `fn` returns the same value as previous (by `!==` comparison), the store won't update. * If `fn` throws an error during execution, the application will crash (this will be fixed in [effector v24](https://github.com/effector/effector/issues/1163)). * Optional `config` parameter: see createStore for details on `skipVoid`. * **Examples** Without transformation function - creates an array store: ```ts import { createStore, combine } from "effector"; const $x = createStore(10); const $y = createStore(20); const $z = createStore(30); const $coordinates = combine([$x, $y, $z]); $coordinates.watch((coords) => console.log(coords)); // => [10, 20, 30] ``` With transformation function - transforms array values: ```ts import { createStore, combine } from "effector"; const $x = createStore(3); const $y = createStore(4); const $distance = combine([$x, $y], ([x, y]) => { return Math.sqrt(x * x + y * y); }); $distance.watch((dist) => console.log(`Distance: ${dist}`)); // => Distance: 5 ``` Practical example - calculating totals from array: ```ts import { createStore, combine } from "effector"; const $itemPrice1 = createStore(10); const $itemPrice2 = createStore(25); const $itemPrice3 = createStore(15); const $cartTotal = combine([$itemPrice1, $itemPrice2, $itemPrice3], (prices) => { return prices.reduce((sum, price) => sum + price, 0); }); $cartTotal.watch((total) => console.log(`Total: $${total}`)); // => Total: $50 ``` Array with different types: ```ts import { createStore, combine } from "effector"; const $userName = createStore("Alice"); const $score = createStore(100); const $isActive = createStore(true); const $playerInfo = combine([$userName, $score, $isActive], ([name, score, active]) => { return `Player ${name}: ${score} points (${active ? "online" : "offline"})`; }); $playerInfo.watch((info) => console.log(info)); // => Player Alice: 100 points (online) ``` Mixing stores with primitives in array: ```ts import { createStore, combine } from "effector"; const $currentPage = createStore(1); const MAX_PAGES = 10; const $pagination = combine([$currentPage, MAX_PAGES], ([current, max]) => { return { current, max, hasNext: current < max, hasPrev: current > 1, }; }); $pagination.watch((pagination) => console.log(pagination)); // => { current: 1, max: 10, hasNext: true, hasPrev: false } ``` * **Return value** Returns a new derived store. ### Related API and Articles * **API** * Store API - Description of stores, their methods and properties * createStore - Creating a new store * sample - Key operator for building connections between units * createEvent - Creating an event * **Articles** * Managing states in effector and how to use derived stores # createApi ```ts import { createApi } from "effector"; ``` `createApi` is a shortcut for generating events connected to a store by supplying an object with for these events. If the source `store` is part of a domain, then the newly created events will also be within that domain. ## Methods ### `createApi(store, api)` #### Formulae ```ts createApi(store, api): objectWithEvents ``` #### Arguments 1. `store` 2. `api` (*Object*) An object with #### Returns (*Object*) An object with events #### Examples ```js import { createStore, createApi } from "effector"; const $playerPosition = createStore(0); // Creating events and attaching them to the store const api = createApi($playerPosition, { moveLeft: (pos, offset) => pos - offset, moveRight: (pos, offset) => pos + offset, }); $playerPosition.watch((pos) => { console.log("position", pos); }); // => position 0 api.moveRight(10); // => position 10 api.moveLeft(5); // => position 5 ``` Try it # createDomain ```ts import { createDomain, type Domain } from "effector"; ``` ## Methods ### `createDomain(name?)` Creates a domain #### Formulae ```typescript createDomain(name?): Domain ``` #### Arguments 1. `name`? (*string*): domain name. Useful for debugging #### Returns : New domain #### Examples ```js import { createDomain } from "effector"; const domain = createDomain(); // Unnamed domain const httpDomain = createDomain("http"); // Named domain const statusCodeChanged = httpDomain.createEvent(); const downloadFx = httpDomain.createEffect(); const apiDomain = httpDomain.createDomain(); // nested domain const $data = httpDomain.createStore({ status: -1 }); ``` Try it # createEffect ## createEffect ```ts import { createEffect } from "effector"; const effectFx = createEffect(); ``` Method for creating effects. Returns a new effect. ### How to Create Effects The `createEffect` method supports several ways to create effects: 1. With a handler - this is the simplest way. 2. With configuration. 3. Without a handler, which can be set later using the .use(handler) method. #### With Handler * **Type** ```ts createEffect( handler: (params: Params) => Done | Promise, ): Effect ``` * **Example** ```ts import { createEffect } from "effector"; const fetchUserReposFx = createEffect(async ({ name }) => { const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }); fetchUserReposFx.done.watch(({ params, result }) => { console.log(result); }); await fetchUserReposFx({ name: "zerobias" }); ``` #### With Configuration The `name` field is used to improve error messages and debugging. * **Type** ```ts export function createEffect(config: { name?: string; handler?: (params: Params) => Promise | Done; }): Effect; ``` * **Example** ```ts import { createEffect } from "effector"; const fetchUserReposFx = createEffect({ name: "fetch user repositories", async handler({ name }) { const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }, }); await fetchUserReposFx({ name: "zerobias" }); ``` #### Without Handler Most commonly used for testing. More detailed information. > WARNING use is an anti-pattern: > > Try to avoid using `.use()`, as it's an anti-pattern and degrades type inference. * **Example** ```ts import { createEffect } from "effector"; const fetchUserReposFx = createEffect(); fetchUserReposFx.use(async ({ name }) => { const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }); await fetchUserReposFx({ name: "zerobias" }); ``` ### Examples * **Updating state on effect completion**: ```ts import { createStore, createEffect } from "effector"; interface Repo { // ... } const $repos = createStore([]); const fetchUserReposFx = createEffect(async (name: string) => { const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }); $repos.on(fetchUserReposFx.doneData, (_, repos) => repos); $repos.watch((repos) => { console.log(`${repos.length} repos`); }); // => 0 repos await fetchUserReposFx("zerobias"); // => 26 repos ``` Run example * **Watching effect state**: ```js import { createEffect } from "effector"; const fetchUserReposFx = createEffect(async ({ name }) => { const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }); fetchUserReposFx.pending.watch((pending) => { console.log(`effect is pending?: ${pending ? "yes" : "no"}`); }); fetchUserReposFx.done.watch(({ params, result }) => { console.log(params); // {name: 'zerobias'} console.log(result); // resolved value, result }); fetchUserReposFx.fail.watch(({ params, error }) => { console.error(params); // {name: 'zerobias'} console.error(error); // rejected value, error }); fetchUserReposFx.finally.watch(({ params, status, result, error }) => { console.log(params); // {name: 'zerobias'} console.log(`handler status: ${status}`); if (error) { console.log("handler rejected", error); } else { console.log("handler resolved", result); } }); await fetchUserReposFx({ name: "zerobias" }); ``` Run example ### Common errors Below is a list of possible errors you may encounter when working with effects: * no handler used in \[effect name] ### Related API and Articles * **API** * Effect API - Description of effects, their methods and properties * sample - Key operator for building connections between units * attach - Creates new effects based on other effects * **Articles** * Working with effects * How to type effects and other units * Guide to testing effects and other units # createEvent ## createEvent ```ts import { createEvent } from "effector"; const event = createEvent(); ``` Method for creating [events][eventApi]. ### Formula ```ts createEvent(eventName?: string): EventCallable createEvent(config: { name?: string sid?: string domain?: Domain }): EventCallable ``` * **Arguments** * `eventName`: Optional argument. Event name for debugging. * `config`: Optional argument. Configuration object. * `name`: Event name. * `sid`: Stable identifier for SSR. * `domain`: Domain for the event. * **Return value** Returns a new callable [event][eventTypes]. ### Examples Updating state by calling an event: ```js import { createStore, createEvent } from "effector"; const addNumber = createEvent(); const $counter = createStore(0); $counter.on(addNumber, (state, number) => state + number); $counter.watch((state) => { console.log("state", state); }); // => 0 addNumber(10); // => 10 addNumber(10); // => 20 addNumber(10); // => 30 ``` Run example We created the `addNumber` event and the `$counter` store, then subscribed to store updates.
    Notice the function call `addNumber(10)`. Every time you call `addNumber(10)`, you can check the console and see how the state changes. Processing data with derived events: ```js import { createEvent } from "effector"; const extractPartOfArray = createEvent(); const array = extractPartOfArray.map((arr) => arr.slice(2)); array.watch((part) => { console.log(part); }); extractPartOfArray([1, 2, 3, 4, 5, 6]); // => [3, 4, 5, 6] ``` Run example ### Common errors Below is a list of possible errors you may encounter when working with events: * call of derived event is not supported, use createEvent instead * unit call from pure function is not supported, use operators like sample instead ### Related API and Articles * **API** * [`Event API`][eventApi] - Event API, its methods, properties and description * [`createApi`][createApi] - Creating a set of events for a store * [`merge`][merge] - Method for combining an array of units into one new event * [`sample`][sample] - Connecting events with other units * **Articles** * [How to work with events][eventGuide] * [How to think in effector and why events matter][mindset] * [Guide to typing events and other units][typescript] [eventApi]: /en/api/effector/Event [eventTypes]: /en/api/effector/Event#event-types [merge]: /en/api/effector/merge [eventGuide]: /en/essentials/events [mindset]: /en/resources/mindset [typescript]: /en/essentials/typescript [sample]: /en/api/effector/sample [createApi]: /en/api/effector/createApi # createStore ## createStore ```ts import { createStore } from "effector"; const $store = createStore(); ``` Method for creating [stores][storeApi]. ### Formula ```ts createStore( defaultState: State, // Initial store state config?: { // Configuration object with additional options skipVoid?: boolean; // Controls updates with undefined values name?: string; // Store name for debugging sid?: string // Stable identifier for SSR updateFilter?: (update: State, current: State) => boolean // Update filtering function serialize?: // Serialization configuration for SSR | 'ignore' | { write: (state: State) => SerializedState read: (json: SerializedState) => State } domain?: Domain; // Domain to which the store belongs }, ): StoreWritable ``` * **Arguments** 1. **`defaultState`**: Initial state 2. **`config`**: Optional configuration object * **`skipVoid`**: Optional argument. Determines whether the [store][storeApi] skips `undefined` values. Default is `true`. If you pass an `undefined` value to a store with `skipVoid: true`, you'll get [an error in the console][storeUndefinedError].

    * **`name`**: Optional argument. Store name. [Babel-plugin][babel] can determine it from the store variable name if the name is not explicitly passed in the configuration.

    * **`sid`**: Optional argument. Unique store identifier. [It's used to distinguish stores between different environments][storeSid]. When using [Babel-plugin][babel], it's set automatically.

    * **`updateFilter`**: Optional argument. A [pure function][pureFn] that prevents store updates if it returns `false`. Should be used when the standard update prevention (if the value to be written to the store equals `undefined` or the current store value) is insufficient.
    * **`serialize`**: Optional argument responsible for store serialization. * `'ignore'`: excludes the store from serialization when calling [serialize][serialize]. * Object with `write` and `read` methods for custom serialization. `write` is called when serialize is invoked and converts the store state to a JSON value – a primitive or simple object/array. `read` is called during fork if the provided `values` are the result of calling [serialize][serialize]. * **Return value** Returns a new [store][storeApi]. ### Examples Basic store usage: ```js import { createEvent, createStore } from "effector"; const addTodo = createEvent(); const clearTodos = createEvent(); const $todos = createStore([]) .on(addTodo, (todos, newTodo) => [...todos, newTodo]) .reset(clearTodos); const $selectedTodos = $todos.map((todos) => { return todos.filter((todo) => !!todo.selected); }); $todos.watch((todos) => { console.log("todos", todos); }); ``` Run example Example with custom `serialize` configuration: ```ts import { createEvent, createStore, serialize, fork, allSettled } from "effector"; const saveDate = createEvent(); const $date = createStore(null, { // Date objects are automatically converted to ISO date strings when calling JSON.stringify // but are not converted back to Date when calling JSON.parse – the result will be the same ISO date string // This will cause state mismatch when hydrating state on the client during server-side rendering // // Custom `serialize` configuration solves this problem serialize: { write: (dateOrNull) => (dateOrNull ? dateOrNull.toISOString() : dateOrNull), read: (isoStringOrNull) => (isoStringOrNull ? new Date(isoStringOrNull) : isoStringOrNull), }, }).on(saveDate, (_, p) => p); const serverScope = fork(); await allSettled(saveDate, { scope: serverScope, params: new Date() }); const serverValues = serialize(serverScope); // `serialize.write` for store `$date` was called console.log(serverValues); // => { nq1e2rb: "2022-11-05T15:38:53.108Z" } // Date object from store saved as ISO date const clientScope = fork({ values: serverValues }); // `serialize.read` for store `$date` was called const currentDate = clientScope.getState($date); console.log(currentDate); // => Date 11/5/2022, 10:40:13 PM // ISO date string converted back to Date object ``` Run example ### Common Errors Below is a list of possible errors you may encounter when working with stores: * [`store: undefined is used to skip updates. To allow undefined as a value provide explicit { skipVoid: false } option`][storeUndefinedError]. * [`serialize: One or more stores dont have sids, their values are omitted`][serializeError]. * [`unit call from pure function is not supported, use operators like sample instead`][unitCallError]. ### Related API and Articles * **API** * [`Store API`][storeApi] - Store API, its methods, properties and description * [`createApi`][createApi] - Creating a set of events for a store * [`combine`][combine] - Creating a new store based on other stores * [`sample`][sample] - Connecting stores with other units * **Articles** * [How to manage state][storeGuide] * [Guide to working with SSR][ssr] * [What is SID and why stores need them][storeSid] * [How to type stores and other units][typescript] [storeApi]: /en/api/effector/Store [storeUndefinedError]: /en/guides/troubleshooting#store-undefined [storeSid]: /en/explanation/sids [ssr]: /en/guides/server-side-rendering [storeGuide]: /en/essentials/manage-states [combine]: /en/api/effector/combine [sample]: /en/api/effector/sample [createApi]: /en/api/effector/createApi [serialize]: /en/api/effector/serialize [typescript]: /en/essentials/typescript [babel]: /en/api/effector/babel-plugin [pureFn]: /en/explanation/glossary/#purity [unitCallError]: /en/guides/troubleshooting#unit-call-from-pure-not-supported [serializeError]: /en/guides/troubleshooting/#store-without-sid # createWatch ```ts import { createWatch } from "effector"; ``` ## Methods ### `createWatch(config)` Creates a subscription on unit (store, event, or effect). #### Formulae ```ts createWatch(config: { unit: Unit fn: (payload: T) => void scope?: Scope }): Subscription ``` #### Arguments 1. `config` (*Object*): Configuration * `unit` (*Unit*): Target unit (store, event of effect) that will be watched * `fn` (*Function*): Function that will be called when the unit is triggered. Accepts the unit's payload as the first argument. * `scope` (): An optional scope object (forked instance) to restrict watcher calls on particular scope. #### Returns : Unsubscribe function #### Examples ##### With scope ```js import { createWatch, createEvent, fork, allSettled } from "effector"; const changeName = createEvent(); const scope = fork(); const unwatch = createWatch({ unit: changeName, scope, fn: console.log }); await allSettled(changeName, { scope, params: "John" }); // output: John changeName("John"); // no output ``` ##### Without scope ```js import { createWatch, createEvent, fork, allSettled } from "effector"; const changeName = createEvent(); const scope = fork(); const unwatch = createWatch({ unit: changeName, fn: console.log }); await allSettled(changeName, { scope, params: "John" }); // output: John changeName("John"); // output: John ``` # debug traces ## Debug Trace import ```ts import "effector/enable_debug_traces"; ``` A special import that enables detailed traces for difficult-to-debug errors, such as a Store missing a proper SID during Scope serialization. > WARNING Performance cost: > > Debug traces work by capturing additional information when Stores and Events are created. > This introduces a performance overhead during module initialization. > > We do not recommend using this API in production environments. To enable debug traces, add `import "effector/enable_debug_traces"` to the entrypoint of your bundle, like this: ```ts // src/index.ts import "effector/enable_debug_traces"; // ...rest of your code ``` ### When to use it If you encounter an error that can be diagnosed with this API, you will see a recommendation in the console: `Add "import 'effector/enable_debug_traces'" to your code entry module to see full stack traces`. Don't forget to remove this import once the issue has been resolved. # fork API [scopeApi]: /en/api/effector/Scope [domainApi]: /en/api/effector/Domain [serializeApi]: /en/api/effector/serialize [allSettledApi]: /en/api/effector/allSettled [hydrateApi]: /en/api/effector/hydrate [sampleApi]: /en/api/effector/sample [storeApi]: /en/api/effector/Store [effectApi]: /en/api/effector/Effect ## `fork` API ```ts import { fork, type Scope } from "effector"; ``` `fork` creates an isolated [scope][scopeApi] of your app. Use it for SSR, testing, or any case where you need to run logic in a safe copy without touching global units. ### How it works 1. Call `fork` to get a new [scope][scopeApi]. 2. If `values` or `handlers` are provided, they are applied when the [scope][scopeApi] is created. 3. Units run in that [scope][scopeApi] operate on isolated state and overridden handlers. 4. Read state via `scope.getState(store)` or serialize it with [`serialize`][serializeApi]. ### `fork` configuration forms |
    Form
    | Description | | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | fork() | Creates a fresh [scope][scopeApi] with no prefilled data or handler overrides. | | fork({ values?, handlers? }) | Creates a [scope][scopeApi] with initial store values and custom effect handlers. | | fork(domain, options?) | Legacy form that requires `domain`. Prefer `fork({ values?, handlers? })` unless you need old signature compatibility. | ### Configurations #### `fork()` Creates a new application [scope][scopeApi] with default behavior. * **Formula** ```ts fork(): Scope ``` * **Type** ```ts type SerializedState = Record; type StorePair = [StoreWritable, T]; export function fork(config?: { values?: StorePair[] | SerializedState; handlers?: Array<[Effect, Function]>; }): Scope; ``` * **Notes** * Returns a fresh [scope][scopeApi] without prefilled stores or handler overrides. * Use when you need a clean state (e.g., first render on the server). * **Example** ```ts import { createStore, createEvent, fork, allSettled } from "effector"; const inc = createEvent(); const dec = createEvent(); const $counter = createStore(0); $counter.on(inc, (value) => value + 1); $counter.on(dec, (value) => value - 1); const scopeA = fork(); const scopeB = fork(); await allSettled(inc, { scope: scopeA }); await allSettled(dec, { scope: scopeB }); console.log($counter.getState()); // => 0 console.log(scopeA.getState($counter)); // => 1 console.log(scopeB.getState($counter)); // => -1 ``` * **Returns** A new [scope][scopeApi]. #### `fork({ values?, handlers? })` Adds initial store values and overrides effect handlers inside the [scope][scopeApi]. * **Formula** ```ts fork({ values?, // e.g. [[$store, value], ...] or serialized object handlers?, // e.g. [[effect, handler], ...] }): Scope ``` * **Type** ```ts type SerializedState = Record; type StorePair = [StoreWritable, T]; export function fork(config?: { values?: StorePair[] | SerializedState; handlers?: Array<[Effect, Function]>; }): Scope; ``` * **Notes** * `values` can be an array of tuples `[$store, value]` or a serialized state object (usually from [`serialize`][serializeApi]); tuples are preferred. * `handlers` accepts only an array of tuples to override effect handlers; overrides apply only within the created [scope][scopeApi]. * Values and handlers are applied once at [scope][scopeApi] creation and are not updated automatically. * **Examples** Set initial state and replace a handler in a test: ```ts import { createEffect, createStore, fork, allSettled } from "effector"; const fetchFriendsFx = createEffect<{ limit: number }, string[]>(async ({ limit }) => { return []; }); const $user = createStore("guest"); const $friends = createStore([]); $friends.on(fetchFriendsFx.doneData, (_, result) => result); const testScope = fork({ values: [[$user, "alice"]], handlers: [[fetchFriendsFx, () => ["bob", "carol"]]], }); await allSettled(fetchFriendsFx, { scope: testScope, params: { limit: 10 }, }); console.log(testScope.getState($friends)); // => ['bob', 'carol'] ``` Create a [scope][scopeApi] from serialized state: ```ts import { fork } from "effector"; const serialized = { userSid: "alice", ageSid: 21, }; const scope = fork({ values: serialized }); ``` * **Returns** A new [scope][scopeApi] with applied `values` and `handlers`. #### `fork(domain, options?)` Legacy form that requires a [domain][domainApi]; keep only for compatibility with older code. > ERROR Deprecated: > > Use `fork({ values?, handlers? })` because `fork` tracks units automatically without a `domain`. * **Formula** ```ts fork(domain, { values?, // e.g. [[$store, value], ...] or serialized object handlers?, // e.g. [[effect, handler], ...] }): Scope ``` * **Type** ```ts type SerializedState = Record; export function fork( domain: Domain, config?: { values?: SerializedState | Array<[StoreWritable, any]>; handlers?: Array<[Effect, Function]>; }, ): Scope; ``` * **Notes** * Pass `domain` only if your project still depends on the old signature. * Allowed `values` and `handlers` formats match the configuration without `domain`. * **Example** ```ts import { createDomain, createStore, fork } from "effector"; const app = createDomain(); const $flag = app.createStore(false); const scope = fork(app, { values: [[$flag, true]], }); console.log(scope.getState($flag)); // => true ``` * **Returns** A new [scope][scopeApi] attached to the provided [domain][domainApi]. ### Related API and articles * **API** * Scope — structure of isolated state * allSettled — run an effect inside a [scope][scopeApi] and await completion * serialize — serialize [scope][scopeApi] state * hydrate — restore state into a [scope][scopeApi] * **Articles** * SSR and working with scope * Testing with an isolated scope * Scope context loss and recovery # forward ## Methods > ERROR Deprecated: > > Since [effector 23.0.0](https://changelog.effector.dev/#effector-23-0-0). > > The core team recommends using sample instead of `forward`. ```ts import { forward, type Subscription } from "effector"; ``` Method to create connection between units in a declarative way. Send updates from one set of units to another. ### `forward({ from, to })` #### Formulae ```ts forward({ from: Unit | Unit[], to: Unit | Unit[] }): Subscription ``` #### Arguments 1. `from` (Unit | Unit\[]): Source of updates. Forward will listen for changes of these units * if an [*Event*][_Event_] is passed, `to` will be triggered on each event trigger and receives event argument * if a [*Store*][_Store_] is passed, `to` will be triggered on each store **change** and receives new value of the store * if an [*Effect*][_Effect_] is passed, `to` will be triggered on each effect call and receives effect parameter * if an array of units is passed, `to` will be triggered when any unit in `from` array is triggered 2. `to` (Unit | Unit\[]): Target for updates. `forward` will trigger these units with data from `from` * if passed an [*Event*][_Event_], it will be triggered with data from `from` unit * if passed a [*Store*][_Store_], data from `from` unit will be written to store and **trigger its update** * if passed an [*Effect*][_Effect_], it will be called with data from `from` unit as parameter * if `to` is an array of units, each unit in that array will be triggered #### Returns Subscription: Unsubscribe function. It breaks connection between `from` and `to`. After call, `to` will not be triggered anymore. > INFO since: > > Arrays of units are supported since [effector 20.6.0](https://changelog.effector.dev/#effector-20-6-0) #### Examples ##### Send store updates to another store ```js import { createStore, createEvent, forward } from "effector"; const $store = createStore(1); const event = createEvent(); forward({ from: event, to: $store, }); $store.watch((state) => console.log("store changed: ", state)); // => store changed: 1 event(200); // => store changed: 200 ``` Try it ##### Forward between arrays of units ```js import { createEvent, forward } from "effector"; const firstSource = createEvent(); const secondSource = createEvent(); const firstTarget = createEvent(); const secondTarget = createEvent(); forward({ from: [firstSource, secondSource], to: [firstTarget, secondTarget], }); firstTarget.watch((e) => console.log("first target", e)); secondTarget.watch((e) => console.log("second target", e)); firstSource("A"); // => first target A // => second target A secondSource("B"); // => first target B // => second target B ``` Try it [_effect_]: /en/api/effector/Effect [_store_]: /en/api/effector/Store [_event_]: /en/api/effector/Event # fromObservable ```ts import { fromObservable, type Observable } from "effector"; ``` ## Methods ### `fromObservable()` Creates an event containing all items from an Observable. #### Formulae ```ts fromObservable(source: Observable): Event ``` #### Arguments 1. `observable` (*Observable*) #### Returns : New event #### Examples ##### Basic use case ```js import { interval } from "rxjs"; import { fromObservable } from "effector"; //emit value in sequence every 1 second const source = interval(1000); const event = fromObservable(source); //output: 0,1,2,3,4,5.... event.watch(console.log); ``` # guard ```ts import { guard } from "effector"; ``` > WARNING Deprecated: > > Since [effector 23.0.0](https://changelog.effector.dev/#effector-23-0-0). > > The core team recommends using sample instead of `guard`. Method for conditional event routing. It provides a way to control one dataflow with the help of another: when the condition and the data are in different places, we can use `guard` with stores as filters to trigger events when condition state is true, thereby modulate signals without mixing them. ## Methods ### `guard({ clock?, source?, filter, target? })` #### Formulae ```ts guard({ clock?, source?, filter, target? }): target ``` > INFO: > > Either `clock` or `source` is required When `clock` is triggered, check `filter` for [truthy] and call `target` with data from `source` if `true`. * If `clock` is not passed, `guard` will be triggered on every `source` update * If `source` is not passed, call `target` with data from `clock` * If `target` is not passed, create with type of `source` and return it from `guard()` * If `filter` is , check it value for [truthy] * If `filter` is `Function`, call it with data from `source` and check result for [truthy] [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy > INFO since: > > `clock` in `guard` is available since [effector 21.8.0](https://changelog.effector.dev/#effector-21-8-0) ### `guard({source, filter, target?})` #### Arguments 1. `params` (*Object*): Configuration object #### Returns , which fires upon `clock` trigger #### Examples ##### Basic ```js import { createStore, createEffect, createEvent, guard } from "effector"; const clickRequest = createEvent(); const fetchRequest = createEffect((n) => new Promise((rs) => setTimeout(rs, 2500, n))); const $clicks = createStore(0).on(clickRequest, (x) => x + 1); const $requestsCount = createStore(0).on(fetchRequest, (x) => x + 1); const $isIdle = fetchRequest.pending.map((pending) => !pending); /* 1. on clickRequest 2. if $isIdle is true 3. take current $clicks value 4. and call fetchRequest with it */ guard({ clock: clickRequest /* 1 */, filter: $isIdle /* 2 */, source: $clicks /* 3 */, target: fetchRequest /* 4 */, }); ``` See ui visualization ##### Function predicate ```js import { createEffect, createEvent, guard } from "effector"; const submitForm = createEvent(); const searchUser = createEffect(); guard({ source: submitForm, filter: (user) => user.length > 0, target: searchUser, }); submitForm(""); // nothing happens submitForm("alice"); // ~> searchUser('alice') ``` Try it ### `guard(source, {filter: booleanStore})` #### Arguments 1. `source` (//): Source unit. Will trigger given `guard` on updates 2. `filter` (): Filter store #### Examples ##### Store filter ```js import { createEvent, createStore, createApi, guard } from "effector"; const trigger = createEvent(); const $unlocked = createStore(true); const { lock, unlock } = createApi($unlocked, { lock: () => false, unlock: () => true, }); const target = guard(trigger, { filter: $unlocked, }); target.watch(console.log); trigger("A"); lock(); trigger("B"); // nothing happens unlock(); trigger("C"); ``` Try it ### `guard(source, {filter: predicate})` #### Arguments 1. `source` (//): Source unit. Will trigger given `guard` on updates 2. `filter` (*(payload) => Boolean*): Predicate function, should be #### Examples ##### Predicate function ```js import { createEvent, guard } from "effector"; const source = createEvent(); const target = guard(source, { filter: (x) => x > 0, }); target.watch(() => { console.log("target called"); }); source(0); // nothing happens source(1); // target called ``` Try it # hydrate ```ts import { hydrate } from "effector"; ``` A companion method for . Hydrates provided values into corresponding stores within a provided domain or scope. The main purpose is an application state hydration on the client side after SSR. ## Methods ### `hydrate(domainOrScope, {values})` > WARNING: > > You need to make sure that the store is created beforehand, otherwise, the hydration might fail. This could be the case if you keep store initialization/hydration scripts separate from stores' creation. #### Formulae ```ts hydrate(domainOrScope: Domain | Scope, { values: Map, any> | {[sid: string]: any} }): void ``` #### Arguments 1. `domainOrScope`: domain or scope which will be filled with given `values` 2. `values`: a mapping from store sids to store values or a Map where keys are store objects and values contain initial store value #### Returns `void` #### Examples Populate store with a predefined value ```js import { createStore, createDomain, fork, serialize, hydrate } from "effector"; const domain = createDomain(); const $store = domain.createStore(0); hydrate(domain, { values: { [$store.sid]: 42, }, }); console.log($store.getState()); // 42 ``` Try it # effector ## Effector API Effector API reference: ### Unit Definitions * Event\ * Effect\ * Store\ * Domain * Scope ### Unit Creators * createEvent() * createStore(default) * createEffect(handler) * createDomain() ### Common Methods * combine(...stores, f) * attach({effect, mapParams, source}) * sample({clock, source, fn, target}) * merge(\[eventA, eventB]) * split(event, cases) * createApi(store, api) ### Fork API * fork() * serialize(scope) * allSettled(unit, { scope }) * scopeBind(event) * hydrate(domain) ### Plugins * effector/babel-plugin * @effector-swc-plugin ### Utilities * is * fromObservable(observable) ### Low Level API * clearNode() * withRegion() * launch() * inspect() ### Import Map Package `effector` provides couple different entry points for different purposes: * effector/compat * effector/inspect * effector/babel-plugin ### Deprecated Methods * forward({from, to}) * guard({source, filter, target}) # inspect ```ts import { inspect } from "effector/inspect"; ``` Special API methods designed to handle debugging and monitoring use cases without giving too much access to internals of your actual app. Useful to create developer tools and production monitoring and observability instruments. ## Inspect API Allows us to track any computations that have happened in the effector's kernel. ### `inspect()` #### Example ```ts import { inspect, type Message } from "effector/inspect"; import { someEvent } from "./app-code"; function logInspectMessage(m: Message) { const { name, value, kind } = m; return console.log(`[${kind}] ${name} ${value}`); } inspect({ fn: (m) => { logInspectMessage(m); }, }); someEvent(42); // will log something like // [event] someEvent 42 // [on] 42 // [store] $count 1337 // ☝️ let's say that reducer adds 1295 to provided number // // and so on, any triggers ``` Scope limits the extent to which computations can be tracked. If no scope is provided - default out-of-scope mode computations will be tracked. ```ts import { fork, allSettled } from "effector"; import { inspect, type Message } from "effector/inspect"; import { someEvent } from "./app-code"; function logInspectMessage(m: Message) { const { name, value, kind } = m; return console.log(`[${kind}] ${name} ${value}`); } const myScope = fork(); inspect({ scope: myScope, fn: (m) => { logInspectMessage(m); }, }); someEvent(42); // ☝️ No logs! That's because tracking was restricted by myScope allSettled(someEvent, { scope: myScope, params: 42 }); // [event] someEvent 42 // [on] 42 // [store] $count 1337 ``` ### Tracing Adding `trace: true` setting allows looking up previous computations, that led to this specific one. It is useful to debug the specific reason for some events happening #### Example ```ts import { fork, allSettled } from "effector"; import { inspect, type Message } from "effector/inspect"; import { someEvent, $count } from "./app-code"; function logInspectMessage(m: Message) { const { name, value, kind } = m; return console.log(`[${kind}] ${name} ${value}`); } const myScope = fork(); inspect({ scope: myScope, trace: true, // <- explicit setting is needed fn: (m) => { if (m.kind === "store" && m.sid === $count.sid) { m.trace.forEach((tracedMessage) => { logInspectMessage(tracedMessage); // ☝️ here we are logging the trace of specific store update }); } }, }); allSettled(someEvent, { scope: myScope, params: 42 }); // [on] 42 // [event] someEvent 42 // ☝️ traces are provided in backwards order, because we are looking back in time ``` ### Errors Effector does not allow exceptions in pure functions. In such case, branch computation is stopped and an exception is logged. There is also a special message type in such case: #### Example ```ts inspect({ fn: (m) => { if (m.type === "error") { // do something about it console.log(`${m.kind} ${m.name} computation has failed with ${m.error}`); } }, }); ``` ## Inspect Graph Allows us to track declarations of units, factories, and regions. ### Example ```ts import { createStore } from "effector"; import { inspectGraph, type Declaration } from "effector/inspect"; function printDeclaration(d: Declaration) { console.log(`${d.kind} ${d.name}`); } inspectGraph({ fn: (d) => { printDeclaration(d); }, }); const $count = createStore(0); // logs "store $count" to console ``` ### `withRegion` Meta-data provided via region's root node is available on declaration. #### Example ```ts import { createNode, withRegion, createStore } from "effector"; import { inspectGraph, type Declaration } from "effector/inspect"; function createCustomSomething(config) { const $something = createStore(0); withRegion(createNode({ meta: { hello: "world" } }), () => { // some code }); return $something; } inspectGraph({ fn: (d) => { if (d.type === "region") console.log(d.meta.hello); }, }); const $some = createCustomSomething({}); // logs "world" ``` # is ```ts import { is, type Unit } from "effector"; ``` Namespace for unit validators. ## Methods ### `is.store(value)` Checks if given value is #### Returns `boolean` — Type-guard #### Examples ```js import { is, createStore, createEvent, createEffect, createDomain } from "effector"; const $store = createStore(null); const event = createEvent(); const fx = createEffect(); is.store($store); // => true is.store(event); // => false is.store(fx); // => false is.store(createDomain()); // => false is.store(fx.pending); // => true is.store(fx.done); // => false is.store($store.updates); // => false is.store(null); // => false ``` Try it ### `is.event(value)` Checks if given value is #### Returns `boolean` — Type-guard #### Examples ```js import { is, createStore, createEvent, createEffect, createDomain } from "effector"; const $store = createStore(null); const event = createEvent(); const fx = createEffect(); is.event($store); // => false is.event(event); // => true is.event(fx); // => false is.event(createDomain()); // => false is.event(fx.pending); // => false is.event(fx.done); // => true is.event($store.updates); // => true is.event(null); // => false ``` Try it ### `is.effect(value)` Checks if given value is #### Returns `boolean` — Type-guard #### Examples ```js import { is, createStore, createEvent, createEffect, createDomain } from "effector"; const $store = createStore(null); const event = createEvent(); const fx = createEffect(); is.effect($store); // => false is.effect(event); // => false is.effect(fx); // => true is.effect(createDomain()); // => false is.effect(null); // => false ``` Try it ### `is.targetable` Checks if given value can be used in operators target (or be called as a function in case of events) #### Returns `boolean` — Type-guard #### Examples ```js import { is, createStore, createEvent, createEffect } from "effector"; const $store = createStore(null); const $mapped = $store.map((x) => x); const event = createEvent(); const mappedEvent = event.map((x) => x); const fx = createEffect(); is.targetable($store); // => true is.targetable($mapped); // => false is.targetable(event); // => true is.targetable(mappedEvent); // => false is.targetable(fx); // => true ``` ### `is.domain(value)` Checks if given value is #### Returns `boolean` — Type-guard #### Examples ```js import { is, createStore, createEvent, createEffect, createDomain } from "effector"; const $store = createStore(null); const event = createEvent(); const fx = createEffect(); is.domain($store); // => false is.domain(event); // => false is.domain(fx); // => false is.domain(createDomain()); // => true is.domain(null); // => false ``` Try it ### `is.scope(value)` > INFO since: > > [effector 22.0.0](https://changelog.effector.dev/#effector-22-0-0) Checks if given value is since [effector 22.0.0](https://changelog.effector.dev/#effector-22-0-0). #### Returns `boolean` — Type-guard #### Examples ```js import { fork } from "effector"; const $store = createStore(null); const event = createEvent(); const fx = createEffect(); const scope = fork(); is.scope(scope); // => true is.scope($store); // => false is.scope(event); // => false is.scope(fx); // => false is.scope(createDomain()); // => false is.scope(null); // => false ``` Try it ### `is.unit(value)` Checks if given value is Unit: Store, Event, Effect, Domain or Scope #### Returns `boolean` — Type-guard #### Examples ```js import { is, createStore, createEvent, createEffect, createDomain, fork } from "effector"; const $store = createStore(null); const event = createEvent(); const fx = createEffect(); const scope = fork(); is.unit(scope); // => true is.unit($store); // => true is.unit(event); // => true is.unit(fx); // => true is.unit(createDomain()); // => true is.unit(fx.pending); // => true is.unit(fx.done); // => true is.unit($store.updates); // => true is.unit(null); // => false ``` Try it ### `is.attached(value)` > INFO since: > > [effector 22.4.0](https://changelog.effector.dev/#effector-22-4-0) Checks if given value is created via method. If passed not an effect, returns `false`. #### Returns `boolean` — Type-guard #### Usage Sometimes you need to add an error log on effects failures, but only on effects that have been "localized" via `attach`. If you leave `onCreateEffect` as it is, without checks, the error log will be duplicated, because it will happen on the parent and the child effect. ```js import { createDomain, attach, is } from "effector"; const logFailuresDomain = createDomain(); logFailuresDomain.onCreateEffect((effect) => { if (is.attached(effect)) { effect.fail.watch(({ params, error }) => { console.warn(`Effect "${effect.compositeName.fullName}" failed`, params, error); }); } }); const baseRequestFx = logFailuresDomain.createEffect((path) => { throw new Error(`path ${path}`); }); const loadDataFx = attach({ mapParams: () => "/data", effect: baseRequestFx, }); const loadListFx = attach({ mapParams: () => "/list", effect: baseRequestFx, }); loadDataFx(); loadListFx(); ``` Try it #### Examples ```js import { is, createStore, createEvent, createEffect, createDomain, attach } from "effector"; const $store = createStore(null); const event = createEvent(); const fx = createEffect(); const childFx = attach({ effect: fx, }); is.attached(childFx); // => true is.attached(fx); // => false is.attached($store); // => false is.attached(event); // => false is.attached(createDomain()); // => false is.attached(null); // => false ``` Try it # launch ```ts import { launch, type Unit, type Node } from "effector"; ``` > INFO since: > > [effector 20.10.0](https://changelog.effector.dev/#effector-20-10-0) ## Methods ### `launch({ target, params })` Low level method for running computation in units (events, effects or stores). Mostly used by library developers for fine-grained control of computations. #### Formulae ```ts launch({ target, params, defer?: boolean, page?: any, scope?: Scope, meta?: Record, }): void ``` #### Arguments TBD #### Returns `void` ### `launch(unit, params)` #### Formulae ```ts launch(unit: Unit | Node, params: T): void ``` #### Returns `void` # merge ```ts import { merge, type Unit } from "effector"; ``` ## Methods ### `merge(units)` > INFO since: > > [effector 20.0.0](https://changelog.effector.dev/#effector-20-0-0) Merges an array of units (events, effects, or stores), returning a new event that triggers upon any of the given units being triggered. ```ts merge(units: Unit[]): Event ``` #### Arguments 1. `units`: An array of units to be merged. #### Returns : A new event that fires when any of the given units is triggered. > TIP: > > In the case of a store, the resulting event will fire upon store updates. #### Types TBD #### Examples ##### Basic Usage ```js import { createEvent, merge } from "effector"; const foo = createEvent(); const bar = createEvent(); const baz = merge([foo, bar]); baz.watch((v) => console.log("merged event triggered: ", v)); foo(1); // => merged event triggered: 1 bar(2); // => merged event triggered: 2 ``` Try it ##### Working with Stores ```js import { createEvent, createStore, merge } from "effector"; const setFoo = createEvent(); const setBar = createEvent(); const $foo = createStore(0).on(setFoo, (_, v) => v); const $bar = createStore(100).on(setBar, (_, v) => v); const anyUpdated = merge([$foo, $bar]); anyUpdated.watch((v) => console.log(`state changed to: ${v}`)); setFoo(1); // => state changed to: 1 setBar(123); // => state changed to: 123 ``` Try it ##### Merging a Store and an Event ```js import { createEvent, createStore, merge } from "effector"; const setFoo = createEvent(); const otherEvent = createEvent(); const $foo = createStore(0).on(setFoo, (_, v) => v); const merged = merge([$foo, otherEvent]); merged.watch((v) => console.log(`merged event payload: ${v}`)); setFoo(999); // => merged event payload: 999 otherEvent("bar"); // => merged event payload: bar ``` Try it # effector/babel-plugin Since Effector allows to automate many common tasks (like setting Stable IDentifiers and providing debug information for Units), there is a built-in plugin for Babel that enhances the developer experience when using the library. ## Usage Please refer to the Babel plugin documentation for usage examples. # effector/compat ```ts import {} from "effector/compat"; ``` The library provides a separate module with compatibility up to IE11 and Chrome 47 (browser for Smart TV devices). > WARNING Bundler, Not Transpiler: > > Since third-party libraries can import `effector` directly, you **should not** use transpilers like Babel to replace `effector` with `effector/compat` in your code because by default, Babel will not transform third-party code. > > **Use a bundler instead**, as it will replace `effector` with `effector/compat` in all modules, including those from third parties. ### Required Polyfills You need to install polyfills for these objects: * `Promise` * `Object.assign` * `Array.prototype.flat` * `Map` * `Set` In most cases, a bundler can automatically add polyfills. #### Vite
    Vite Configuration Example ```js import { defineConfig } from "vite"; import legacy from "@vitejs/plugin-legacy"; export default defineConfig({ plugins: [ legacy({ polyfills: ["es.promise", "es.object.assign", "es.array.flat", "es.map", "es.set"], }), ], }); ```
    ## Usage ### Manual Replacement You can use `effector/compat` instead of the `effector` package if you need to support old browsers. ```diff - import {createStore} from 'effector' + import {createStore} from 'effector/compat' ``` ### Automatic Replacement However, you can set up your bundler to automatically replace `effector` with `effector/compat` in your code. #### Webpack
    Webpack Configuration Example ```js module.exports = { resolve: { alias: { effector: "effector/compat", }, }, }; ```
    #### Vite
    Vite Configuration Example ```js import { defineConfig } from "vite"; export default defineConfig({ resolve: { alias: { effector: "effector/compat", }, }, }); ```
    # effector/inspect ## `effector/inspect` Effector has special API methods designed to handle debugging and monitoring use cases without giving too much access to the internals of your actual app — Inspect API. ### Why a Separate Module? Inspect API is designed to be disposable. By design, any feature that uses Inspect API can be removed from the production build without any side effects. To emphasize this, Inspect API is not included in the main module. Instead, it's available in a separate module `effector/inspect`. ### Usage Please refer to Inspect API docs for usage examples. # restore ```ts import { restore } from "effector"; ``` ## Methods ### `restore(event, defaultState)` Creates a from an . It works like a shortcut for `createStore(defaultState).on(event, (_, payload) => payload)` > WARNING It is not a derived store: > > Restore creates a new store. It is not a DerivedStore. That means you can modify its state via events, and use it as `target` in sample. #### Formulae ```ts restore(event: Event, defaultState: T): StoreWritable ``` #### Arguments 1. `event` 2. `defaultState` (*Payload*) #### Returns : New store #### Examples ##### Basic ```js import { createEvent, restore } from "effector"; const event = createEvent(); const $store = restore(event, "default"); $store.watch((state) => console.log("state: ", state)); // state: default event("foo"); // state: foo ``` Try it ### `restore(effect, defaultState)` Creates a out of successful results of an . It works like a shortcut for `createStore(defaultState).on(effect.done, (_, {result}) => result)` #### Formulae ```ts restore(effect: Effect, defaultState: Done): StoreWritable ``` #### Arguments 1. `effect` 2. `defaultState` (*Done*) #### Returns : New store #### Types Store will have the same type as `Done` from `Effect`. Also, `defaultState` should have `Done` type. #### Examples ##### Effect ```js import { createEffect, restore } from "effector"; const fx = createEffect(() => "foo"); const $store = restore(fx, "default"); $store.watch((state) => console.log("state: ", state)); // => state: default await fx(); // => state: foo ``` Try it ### `restore(shape)` Creates an object with stores from an object with values. #### Formulae TBD #### Arguments 1. `shape` (*State*) #### Returns : New store. #### Examples ##### Object ```js import { restore } from "effector"; const { foo: $foo, bar: $bar } = restore({ foo: "foo", bar: 0, }); $foo.watch((foo) => { console.log("foo", foo); }); // => foo 'foo' $bar.watch((bar) => { console.log("bar", bar); }); // => bar 0 ``` Try it # sample API [units]: /en/explanation/glossary#common-unit [eventApi]: /en/api/effector/Event [storeApi]: /en/api/effector/Store [effectApi]: /en/api/effector/Effect [purity]: /en/explanation/glossary/#purity ## `sample` API ```ts import { sample } from "effector"; ``` The `sample` method is used to connect units. Its main purpose is to take data from one place `source` and send it to another `target` when a certain trigger `clock` occurs. A common use case is when you need to process an event using data from a store. Instead of using `store.getState()`, which can lead to inconsistent state, it's better to use `sample`. > TIP how to work with sample: > > Learn how to compose units and use the ### How it works * When `clock` triggers, the value from `source` is read. * If a `filter` is specified and returns `true`, or if it's a store with `true` value, processing continues. * If a `fn` is provided, data is transformed. * Data is then passed to the `target`. ### Special behavior of `sample` * If `clock` is not provided, `sample` will trigger on every update of `source`. * If `target` is not provided, `sample` will create and return a new derived [unit][units]. ### Returned unit and value If `target` is not provided, it will be created at runtime. The type of unit returned depends on this table: | clock \ source | | | | | ----------------------------------- | --------------------------------- | --------------------------------- | ----------------------------------- | | | `Store` | `Event` | `Event` | | | `Event` | `Event` | `Event` | | | `Event` | `Event` | `Event` | How to use this table: 1. Pick the type of `clock` (column). 2. Pick the type of `source` (row). 3. The intersecting cell shows the return type. If `target` is explicitly provided, then that `target` is returned. Example: ```ts const event = createEvent(); const $store = createStore(); const $secondStore = createStore(); const $derivedStore = sample({ clock: $store, source: $secondStore, }); // Returns a derived store because both clock and source are stores const derivedEvent = sample({ clock: event, source: $store, }); // Returns a derived event because the clock is an event ``` ### Full form * **Formula** ```ts sample({ clock?, // trigger source?, // data source filter?, // filter predicate fn?, // transformation function target?, // target unit batch?, // batching flag name? // unit name }) ``` #### `clock` A trigger unit that determines when to sample the source.
    Optional. * **Type** ```ts sample({ clock?: Unit | Unit[], }) ``` Can be: * [`Event`][eventApi] — triggers on event call * [`Store`][storeApi] — triggers on store update * [`Effect`][effectApi] — triggers on effect execution * `Unit[]` — triggers when any unit in the array is triggered > INFO either clock or source required: > > Although the `clock` argument is optional, when using the `sample` method you must provide either `clock` or source. ```ts const clicked = createEvent(); const $store = createStore(0); const fetchFx = createEffect(); sample({ source: $data, clock: clicked, }); sample({ source: $data, clock: $store, }); sample({ source: $data, clock: [clicked, fetchFx.done], }); ``` *** #### `source` The data source to be read when the `clock` unit triggers. If `clock` is not provided, then `source` is used as the `clock`. Optional. * **Type** ```ts sample({ source?: Unit | Unit[] | { [key: string]: Unit }, }) ``` Can be: * [`Store`][storeApi] — reads the current value of the store * [`Event`][eventApi] — takes the most recent payload from the event * [`Effect`][effectApi] — takes the most recent payload from the effect call * Object of [units][units] — for combining multiple sources * Array of [units][units] — for combining multiple sources > INFO either source or clock required: > > Although the `source` argument is optional, when using the `sample` method you must provide either `source` or clock. *** #### `filter` A predicate function or store used to filter the data. If it returns `false` (or is a store that holds `false`), the data will not be passed to `target`. Optional. * **Type** ```ts sample({ filter?: Store | (source: Source, clock: Clock) => (boolean | Store), }) ``` Can be: * [`Store`][storeApi] — a boolean store (either base or derived) * Predicate function — returns a `boolean` value ```ts const $isUserActive = createStore(false); sample({ clock: checkScore, source: $score, filter: (score) => score > 100, target: showWinnerFx, }); sample({ clock: action, source: $user, filter: $isUserActive, target: adminActionFx, }); ``` *** #### `fn` A function used to transform the data before passing it to the `target`. The function **must be pure**. Optional. * **Type** ```ts sample({ fn?: (source: Source, clock: Clock) => Target }) ``` > INFO returned data type: > > The type of data returned must match the type of data in `target`. ```ts const $user = createStore({}); const saveUserFx = createEffect((user: User) => { // ... }); sample({ clock: updateProfile, source: $user, fn: (user, updates) => ({ ...user, ...updates }), target: saveUserFx, }); sample({ clock: submit, source: $form, fn: (form) => form.email, target: sendEmailFx, }); ``` *** #### `target` The destination unit that will receive the data and be triggered. Optional. * **Type** ```ts sample({ target?: Unit | Unit[], }) ``` Can be: * EventCallable\ — a regular event (not derived) that will be called * [`Effect`][effectApi] — an effect that will be triggered * StoreWritable\ — a writable store that will be updated * `Unit[]` — all units in the array will be called > INFO target without target: > > If `target` is not specified, `sample` returns a new derived unit. ```ts const targetEvent = createEvent(); const targetFx = createEffect(); const $targetStore = createStore(""); // Event as target sample({ source: $store, clock: trigger, target: targetEvent, }); // Effect as target sample({ source: $store, clock: trigger, target: targetFx, }); // Store as target sample({ source: $store, clock: trigger, target: $targetStore, }); ``` *** #### `greedy` > WARNING Deprecated: > > As of effector 23.0.0, the `greedy` property is deprecated. > > Use `batch` instead of `greedy`. *** #### `batch` Enables batching of updates for better performance. Default is `true`. Optional. * **Type** ```ts sample({ batch?: boolean // Default: true }) ``` *** #### `name` The `name` field allows you to assign a debug-friendly name to the created unit. Optional. * **Type** ```ts sample({ name?: string }) ``` ### Short Form * **Formula** ```ts sample(source, clock, fn?): Unit ``` This is a shorthand version of the `sample` method, which always implicitly returns a `target`. It supports multiple patterns: 1. All arguments: `sample(source, clock, fn)` — with a transformation function 2. Just `source` and `clock`: `sample(source, clock)` — no transformation function 3. `source` and `fn`: `sample(source, fn)` — no `clock`, so `source` acts as the trigger 4. One argument: `sample(source)` — only `source`, which acts as the trigger and the source * **Return value** The return type depends on the combination of units used and the return type of fn, if present. Otherwise, it falls back to the `source`. *** #### `source` Acts as the data source when the `clock` triggers. If no `clock` is provided, `source` is used as the trigger. * **Type** ```ts sample(source?: Unit | Unit[]) ``` Can be: * [`Store`][storeApi] — current value of the store * [`Event`][eventApi] — last triggered payload * [`Effect`][effectApi] — last payload sent to the effect * `Unit[]` — array of [units][units] that triggers when any unit is activated > INFO behavior without clock: > > If `clock` is not specified, then `source` behaves as `clock` - that is, it acts as the trigger. *** #### `clock` The unit that acts as the trigger to read from source. Optional. * **Type** ```ts sample(clock?: Unit | Unit[]) ``` Can be: * [`Event`][eventApi] — triggered on event call * [`Store`][storeApi] — triggered on store update * [`Effect`][effectApi] — triggered on effect execution * `Unit[]` — triggers on any unit in the array ```ts const clicked = createEvent(); const $store = createStore(0); const fetchFx = createEffect(); sample($data, clicked); sample($data, $store); ``` *** #### `fn` A transformation function to be applied before sending the result to the implicit target. The function must be [**pure**][purity]. Optional. * **Type** ```ts sample(fn: (source: Source, clock: Clock) => result) ``` * **Example** ```ts const $userName = createStore("john"); const submitForm = createEvent(); const sampleUnit = sample( $userName /* 2 */, submitForm /* 1 */, (name, password) => ({ name, password }) /* 3 */, ); submitForm(12345678); // 1. submitForm is triggered with 12345678 // 2. $userName value is read ("john") // 3. The values are transformed and passed to sampleUnit ``` *** ### Related APIs and Articles * **API** * merge — Combines updates from an array of units * Store — Store description with methods and properties * Event — Event description with methods and properties * Effect — Effect description with methods and properties * **Articles** * Typing units and methods * Unit composition and working with # scopeBind ```ts import { scopeBind } from "effector"; ``` `scopeBind` is a method to bind a unit (an Event or Effect) to a Scope to be called later. Effector supports imperative calling of events within watchers, however, there are instances where you must explicitly bind events to the scope, such as when triggering events from within `setTimeout` or `setInterval` callbacks. ## Methods ### `scopeBind(event, options?)` #### Formulae ```ts scopeBind(event: EventCallable): (payload: T) => void scopeBind(event: EventCallable, options?: {scope?: Scope, safe?: boolean}): (payload: T) => void ``` #### Arguments 1. `event` or to be bound to the scope. 2. `options` (*Object*): Optional configuration. * `scope` (*Scope*): Scope to bind event to. * `safe` (*Boolean*): Flag for exception suppression if there is no scope. #### Returns `(payload: T) => void` — A function with the same types as `event`. #### Examples ##### Basic Usage We are going to call `changeLocation` inside `history.listen` callback so there is no way for effector to associate event with corresponding scope, and we should explicitly bind event to scope using `scopeBind`. ```ts import { createStore, createEvent, attach, scopeBind } from "effector"; const $history = createStore(history); const initHistory = createEvent(); const changeLocation = createEvent(); const installHistoryFx = attach({ source: $history, effect: (history) => { const locationUpdate = scopeBind(changeLocation); history.listen((location) => { locationUpdate(location); }); }, }); sample({ clock: initHistory, target: installHistoryFx, }); ``` See full example ### `scopeBind(callback, options?)` Binds arbitrary callback to a scope to be called later. The bound version of the function retains all properties of the original, e.g., if the original function would throw when called with a certain argument, the bound version will also throw under the same circumstances. > INFO since: > > Feature is available since `effector 23.1.0` release. > Multiple function arguments are supported since `effector 23.3.0` > WARNING: > > To be compatible with the Fork API, callbacks **must** adhere to the same rules as `Effect` handlers: > > * Synchronous functions can be used as they are. > * Asynchronous functions must follow the rules described in "Imperative Effect calls with scope". #### Formulae ```ts scopeBind(callback: (...args: Args) => T, options?: { scope?: Scope; safe?: boolean }): (...args: Args) => T; ``` #### Arguments 1. `callback` (*Function*): Any function to be bound to the scope. 2. `options` (*Object*): Optional configuration. * `scope` (*Scope*): Scope to bind the event to. * `safe` (*Boolean*): Flag for exception suppression if there is no scope. #### Returns `(...args: Args) => T` — A function with the same types as `callback`. #### Examples ```ts import { createEvent, createStore, attach, scopeBind } from "effector"; const $history = createStore(history); const locationChanged = createEvent(); const listenToHistoryFx = attach({ source: $history, effect: (history) => { return history.listen( scopeBind((location) => { locationChanged(location); }), ); }, }); ``` # serialize ```ts import { serialize, type Scope } from "effector"; ``` ## Methods ### `serialize(scope, params)` A companion method for . It allows us to get a serialized value for all the store states within a scope. The main purpose is an application state serialization on the server side during SSR. > WARNING Requirements: > > or is required for using this method, as these plugins provide the SIDs for stores, which are required for stable state serialization. > > You can find deep-dive explanation here #### Formulae ```ts serialize(scope: Scope, { ignore?: Array>; onlyChanges?: boolean }): {[sid: string]: any} ``` #### Arguments 1. `scope` : a scope object (forked instance) 2. `ignore` Optional array of to be omitted during serialization (added 20.14.0) 3. `onlyChanges` Optional boolean flag to ignore stores which didn't change in fork (prevent default values from being carried over network) > WARNING Deprecated: > > Since [effector 23.0.0](https://changelog.effector.dev/#effector-23-0-0) property `onlyChanges` is deprecated. #### Returns An object with store values using sids as a keys > WARNING Reminder: > > If a store does not have a sid, its value will be omitted during serialization. #### Examples ##### Serialize forked instance state ```js import { createStore, createEvent, allSettled, fork, serialize } from "effector"; const inc = createEvent(); const $store = createStore(42); $store.on(inc, (x) => x + 1); const scope = fork(); await allSettled(inc, { scope }); console.log(serialize(scope)); // => {[sid]: 43} ``` Try it ##### Using with `onlyChanges` With `onlyChanges`, this method will serialize only stores which were changed by some trigger during work or defined in `values` field by fork or hydrate(scope). Once being changed, a store will stay marked as changed in given scope even if it was turned back to the default state during work, otherwise client will not update that store on its side, which is unexpected and inconsistent. This allows us to hydrate client state several times, for example, during route changes in next.js ```js import { createDomain, fork, serialize, hydrate } from "effector"; const app = createDomain(); /** store which we want to hydrate by server */ const $title = app.createStore("dashboard"); /** store which is not used by server */ const $clientTheme = app.createStore("light"); /** scope in client app */ const clientScope = fork(app, { values: new Map([ [$clientTheme, "dark"], [$title, "profile"], ]), }); /** server side scope of chats page created for each request */ const chatsPageScope = fork(app, { values: new Map([[$title, "chats"]]), }); /** this object will contain only $title data * as $clientTheme never changed in server scope */ const chatsPageData = serialize(chatsPageScope, { onlyChanges: true }); console.log(chatsPageData); // => {'-l644hw': 'chats'} /** thereby, filling values from a server will touch only relevant stores */ hydrate(clientScope, { values: chatsPageData }); console.log(clientScope.getState($clientTheme)); // => dark ``` Try it # split ```ts import { split } from "effector"; ``` Choose one of cases by given conditions. It "splits" source unit into several events, which fires when payload matches their conditions. Works like pattern matching for payload values and external stores ## Concepts ### Case mode Mode in which target case is selected by the name of its field. Case could be selected from data in `source` by case function or from external case store which kept current case name. After selection data from `source` will be sent to corresponding `cases[fieldName]` (if there is one), if none of the fields matches, then the data will be sent to `cases.__` (if there is one). **See also**: * case store * case function ### Matching mode Mode in which each case is sequentially matched by stores and functions in fields of `match` object. If one of the fields got `true` from store value or return of function, then the data from `source` will be sent to corresponding `cases[fieldName]` (if there is one), if none of the fields matches, then the data will be sent to `cases.__` (if there is one) **See also**: * matcher store * matcher function ### Case store Store with a string which will be used to choose the case by its name. Placed directly in `match` field. ```ts split({ source: Unit // case store match: Store<'first' | 'second'>, cases: { first: Unit | Unit[], second: Unit | Unit[], __?: Unit | Unit[] } }) ``` ### Case function String-returning function which will be called with value from `source` to choose the case by its name. Placed directly in `match` field, should be ```ts split({ source: Unit // case function match: (value: T) => 'first' | 'second', cases: { first: Unit | Unit[], second: Unit | Unit[], __?: Unit | Unit[] } }) ``` ### Matcher store Boolean store which indicates whether to choose the particular case or try the next one. Placed in fields of `match` object, might be mixed with matcher functions ```ts split({ source: Unit match: { // matcher store first: Store, second: Store }, cases: { first: Unit | Unit[], second: Unit | Unit[], __?: Unit | Unit[] } }) ``` ### Matcher function > INFO: > > Case store, case function and matcher store are supported since [effector 21.8.0](https://changelog.effector.dev/#effector-21-8-0) Boolean-returning function which indicates whether to choose the particular case or try the next one. Placed in fields of `match` object, might be mixed with matcher stores, should be ```ts split({ source: Unit match: { // matcher function first: (value: T) => boolean, second: (value: T) => boolean }, cases: { first: Unit | Unit[], second: Unit | Unit[], __?: Unit | Unit[] } }) ``` ## Methods ### `split({ source, match, cases })` > INFO since: > > [effector 21.0.0](https://changelog.effector.dev/#effector-21-0-0) #### Formulae ```ts split({ source, match, cases }); ``` ```ts split({ source: Unit // case function match: (data: T) => 'a' | 'b', cases: { a: Unit | Unit[], b: Unit | Unit[], __?: Unit | Unit[] } }) split({ source: Unit // case store match: Store<'a' | 'b'>, cases: { a: Unit | Unit[], b: Unit | Unit[], __?: Unit | Unit[] } }) split({ source: Unit match: { // matcher function a: (data: T) => boolean, // matcher store b: Store }, cases: { a: Unit | Unit[], b: Unit | Unit[], __?: Unit | Unit[] } }) ``` #### Arguments * `source`: Unit which will trigger computation in `split` * `match`: Single store with string, single function which returns string or object with boolean stores and functions which returns boolean * `cases`: Object with units or arrays of units to which data will be passed from `source` after case selection #### Returns `void` #### Examples ##### Basic ```js import { split, createEffect, createEvent } from "effector"; const messageReceived = createEvent(); const showTextPopup = createEvent(); const playAudio = createEvent(); const reportUnknownMessageTypeFx = createEffect(({ type }) => { console.log("unknown message:", type); }); split({ source: messageReceived, match: { text: (msg) => msg.type === "text", audio: (msg) => msg.type === "audio", }, cases: { text: showTextPopup, audio: playAudio, __: reportUnknownMessageTypeFx, }, }); showTextPopup.watch(({ value }) => { console.log("new message:", value); }); messageReceived({ type: "text", value: "Hello", }); // => new message: Hello messageReceived({ type: "image", imageUrl: "...", }); // => unknown message: image ``` Try it ##### Direct match You can match directly to store api as well: ```js import { split, createStore, createEvent, createApi } from "effector"; const messageReceived = createEvent(); const $textContent = createStore([]); split({ source: messageReceived, match: { text: (msg) => msg.type === "text", audio: (msg) => msg.type === "audio", }, cases: createApi($textContent, { text: (list, { value }) => [...list, value], audio: (list, { duration }) => [...list, `audio ${duration} ms`], __: (list) => [...list, "unknown message"], }), }); $textContent.watch((messages) => { console.log(messages); }); messageReceived({ type: "text", value: "Hello", }); // => ['Hello'] messageReceived({ type: "image", imageUrl: "...", }); // => ['Hello', 'unknown message'] messageReceived({ type: "audio", duration: 500, }); // => ['Hello', 'unknown message', 'audio 500 ms'] ``` Try it ##### Cases with arrays of units ```js import { createEffect, createEvent, createStore, sample, split } from "effector"; const $verificationCode = createStore("12345"); const $error = createStore(""); const modalToInputUsername = createEvent(); const modalToAuthorizationMethod = createEvent(); const checkVerificationCodeFx = createEffect((code) => { throw "500"; }); sample({ clock: verificationCodeSubmitted, source: $verificationCode, target: checkVerificationCodeFx, }); split({ source: checkVerificationCodeFx.failData, match: (value) => (["400", "410"].includes(value) ? "verificationCodeError" : "serverError"), cases: { verificationCodeError: $verificationCodeError, serverError: [$error, modalToAuthorizationMethod], }, }); $error.updates.watch((value) => console.log("ERROR: " + value)); modalToAuthorizationMethod.watch(() => console.log("Modal window to the authorization method content."), ); // => ERROR: 500 // => Modal window to the authorization method content. ``` ### `split(source, match)` > INFO since: > > [effector 20.0.0](https://changelog.effector.dev/#effector-20-0-0) #### Formulae ```ts split(source, match); ``` #### Arguments 1. `source`: Unit which will trigger computation in `split` 2. `match` (*Object*): Schema of cases, which uses names of resulting events as keys, and matching function\*((value) => Boolean)\* #### Returns (Object) – Object, having keys, defined in `match` argument, plus `__`(two underscores) – which stands for `default` (no matches met) case. #### Examples ##### Basic ```js import { createEvent, split } from "effector"; const message = createEvent(); const messageByAuthor = split(message, { bob: ({ user }) => user === "bob", alice: ({ user }) => user === "alice", }); messageByAuthor.bob.watch(({ text }) => { console.log("[bob]: ", text); }); messageByAuthor.alice.watch(({ text }) => { console.log("[alice]: ", text); }); message({ user: "bob", text: "Hello" }); // => [bob]: Hello message({ user: "alice", text: "Hi bob" }); // => [alice]: Hi bob /* default case, triggered if no one condition met */ const { __: guest } = messageByAuthor; guest.watch(({ text }) => { console.log("[guest]: ", text); }); message({ user: "unregistered", text: "hi" }); // => [guest]: hi ``` Try it > INFO: > > Only the first met match will trigger resulting event ##### Another ```js import { createEvent, split } from "effector"; const message = createEvent(); const { short, long, medium } = split(message, { short: (m) => m.length <= 5, medium: (m) => m.length > 5 && m.length <= 10, long: (m) => m.length > 10, }); short.watch((m) => console.log(`short message '${m}'`)); medium.watch((m) => console.log(`medium message '${m}'`)); long.watch((m) => console.log(`long message '${m}'`)); message("Hello, Bob!"); // => long message 'Hello, Bob!' message("Hi!"); // => short message 'Hi!' ``` Try it ### `split({ source, clock?, match, cases })` > INFO since: > > [effector 22.2.0](https://changelog.effector.dev/#effector-22-2-0) It works the same as split with cases, however computations in `split` will be started after `clock` is triggered. #### Formulae ```js split({source, clock?, match, cases}) ``` #### Arguments TBD #### Examples ```js import { createStore, createEvent, createEffect, split } from "effector"; const options = ["save", "delete", "forward"]; const $message = createStore({ id: 1, text: "Bring me a cup of coffee, please!" }); const $mode = createStore(""); const selectedMessageOption = createEvent(); const saveMessageFx = createEffect(() => "save"); const forwardMessageFx = createEffect(() => "forward"); const deleteMessageFx = createEffect(() => "delete"); $mode.on(selectedMessageOption, (mode, opt) => options.find((item) => item === opt) ?? mode); split({ source: $message, clock: selectedMessageOption, match: $mode, cases: { save: saveMessageFx, delete: deleteMessageFx, forward: forwardMessageFx, }, }); selectedMessageOption("delet"); // nothing happens selectedMessageOption("delete"); ``` Try it # SWC plugin An official SWC plugin can be used for SSR and easier debugging experience in SWC-powered projects, like [Next.js](https://nextjs.org) or Vite with [vite-react-swc plugin](https://github.com/vitejs/vite-plugin-react-swc). The plugin has the same functionality as the built-in babel-plugin. It provides all Units with unique `SID`s (Stable Identifier) and name, as well as other debug information. > WARNING Unstable: > > This SWC plugin, along with all other SWC plugins, is currently considered experimental and unstable. > > SWC and Next.js might not follow semver when it comes to plugin compatibility. ## Installation Install @effector/swc-plugin using your preferred package manager. ```bash npm install -ED @effector/swc-plugin ``` ### Versioning To avoid compatibility issues caused by breaking changes in SWC or Next.js, this plugin publishes different ['labels'](https://semver.org/#spec-item-9) for different underlying `@swc/core`. Refer to the table below to choose the correct plugin version for your setup. > TIP: > > For better stability, we recommend pinning both your runtime (like Next.js or `@swc/core`) and the `@effector/swc-plugin` version. > > Use the `--exact`/`--save-exact` option in your package manager to install specific, compatible versions. This ensures updates to one dependency don't break your application. | `@swc/core` version | Next.js version | Correct plugin version | | ------------------- | ---------------------------------------- | ---------------------- | | `>=1.4.0 <1.6.0` | `>=14.2.0 <=14.2.15` | `@swc1.4.0` | | `>=1.6.0 <1.7.0` | `>=15.0.0-canary.37 <=15.0.0-canary.116` | `@swc1.6.0` | | `>=1.7.0 <1.8.0` | `>=15.0.0-canary.122 <=15.0.2` | `@swc1.7.0` | | `>=1.9.0 <1.10.0` | `>=15.0.3 <15.2.0` | `@swc1.9.0` | | `>=1.10.0 <1.11.0` | `>=15.2.0 <15.2.1` | `@swc1.10.0` | | `>=1.11.0` | `>=15.2.1 <15.4.0` | `@swc1.11.0` | | `>=1.12.0` | `>=15.4.0` | `@swc1.12.0` | For more information on compatibility, refer to the SWC documentation on [Selecting the SWC Version](https://swc.rs/docs/plugin/selecting-swc-core) and interactive [compatibility table](https://plugins.swc.rs) on SWC website. ## Usage To use the plugin, simply add it to your tool's configuration. ### Next.js If you're using the [Next.js Compiler](https://nextjs.org/docs/architecture/nextjs-compiler) powered by SWC, add this plugin to your `next.config.js`. ```js const nextConfig = { experimental: { // even if empty, pass an options object `{}` to the plugin swcPlugins: [["@effector/swc-plugin", {}]], }, }; ``` You'll also need to install the official [`@effector/next`](https://github.com/effector/next) bindings to enable SSR/SSG. > WARNING Turbopack: > > Note that some functionality may be broken when using Turbopack with Next.js, especially with relative . Use at your own risk. ### .swcrc Add a new entry to `jsc.experimental.plugins` option in your `.swcrc`. ```json { "$schema": "https://json.schemastore.org/swcrc", "jsc": { "experimental": { "plugins": [["@effector/swc-plugin", {}]] } } } ``` ## Configuration ### `factories` Specify an array of module names or files to treat as custom factories. When using SSR, factories is required for ensuring unique SIDs across your application. > TIP: > > Community packages ([`patronum`](https://patronum.effector.dev), [`@farfetched/core`](https://ff.effector.dev/), [`atomic-router`](https://atomic-router.github.io/), [`effector-action`](https://github.com/AlexeyDuybo/effector-action) and [`@withease/factories`](https://withease.effector.dev/factories/)) are always enabled, so you don't need to list them explicitly. #### Formulae ```json ["@effector/swc-plugin", { "factories": ["./path/to/factory", "factory-package"] }] ``` * Type: `string[]` * Default: `[]` If you provide a relative path (starting with `./`), the plugin treats it as a local factory relative to your project's root directory. These factories can only be imported using relative imports within your code. Otherwise, if you specify a package name or TypeScript alias, it's interpreted as an exact import specifier. You must use such import exactly as specified in configuration. #### Examples ```json ["@effector/swc-plugin", { "factories": ["./src/factory"] }] ``` ```ts title="/src/factory.ts" import { createStore } from "effector"; /* createBooleanStore is a factory */ export const createBooleanStore = () => createStore(true); ``` ```ts title="/src/widget/user.ts" import { createBooleanStore } from "../factory"; const $boolean = createBooleanStore(); /* Treated as a factory! */ ``` ### `debugSids` Append the full file path and Unit name to generated `SID`s for easier debugging of SSR issues. #### Formulae ```json ["@effector/swc-plugin", { "debugSids": false }] ``` * Type: `boolean` * Default: `false` ### `hmr` > INFO Since: > > `@effector/swc-plugin@0.7.0` Enable Hot Module Replacement (HMR) support to clean up links, subscriptions and side effects managed by Effector. This prevents double-firing of Effects and watchers. > WARNING Interaction with factories: > > Hot Module Replacement works best when all factories in the project are properly declared. A correct configuration allows the plugin to detect what code should be cleaned up during hot reload. #### Formulae ```json ["@effector/swc-plugin", { "hmr": "es" }] ``` * Type: `"es"` | `"cjs"` | `false` * `"es"`: Use `import.meta.hot` HMR API in bundlers that are ESM-compliant, like Vite and Rollup * `"cjs"`: Use `module.hot` HMR API in bundlers that rely on CommonJS modules, like Webpack, Next.js or Metro (React Native) * `false`: Disable Hot Module Replacement * Default: `false` > INFO In Production: > > When bundling for production, make sure to set the `hmr` option to `false` to reduce bundle size and improve runtime performance. ### `addNames` Add names to Units when calling factories (like `createStore` or `createDomain`). This is helpful for debugging during development and testing, but its recommended to disable it for minification. #### Formulae ```json ["@effector/swc-plugin", { "addNames": true }] ``` * Type: `boolean` * Default: `true` ### `addLoc` Include location information (file paths and line numbers) for Units and factories. This is useful for debugging with tools like [`effector-logger`](https://github.com/effector/logger). #### Formulae ```json ["@effector/swc-plugin", { "addLoc": true }] ``` * Type: `boolean` * Default: `false` ### `forceScope` Inject `forceScope: true` into all hooks or `@effector/reflect` calls to ensure your app always uses `Scope` during rendering. If `Scope` is missing, an error will be thrown, eliminating the need for `/scope` or `/ssr` imports. > INFO Note: > > Read more about Scope enforcement in the `effector-react` documentation. #### Formulae ```json [ "@effector/swc-plugin", { "forceScope": { "hooks": true, "reflect": false } } ] ``` * Type: `boolean | { hooks: boolean, reflect: boolean }` * Default: `false` ##### `hooks` Enforces all hooks from effector-react and effector-solid, like `useUnit` and `useList`, to use `Scope` in runtime. ##### `reflect` > INFO Since: > > Supported by `@effector/reflect` since 9.0.0 For [`@effector/reflect`](https://github.com/effector/reflect) users, enforces all components created with `reflect` library use `Scope` in runtime. ### `transformLegacyDomainMethods` When enabled (default), this option transforms Unit creators in Domains, like `domain.event()` or `domain.createEffect()`. However, this transformation can be unreliable and may affect unrelated code. If that's the case for you, disabling this option can fix these issues. Disabling this option will **stop** adding SIDs and other debug information to these unit creators. Ensure your code does not depend on domain methods before disabling. > TIP: > > Instead of using unit creators directly on domain, consider using the `domain` argument in regular methods. #### Formulae ```json ["@effector/swc-plugin", { "transformLegacyDomainMethods": false }] ``` * Type: `boolean` * Default: `true` # withRegion ```ts import { withRegion } from "effector"; ``` The method is based on the idea of region-based memory management (see [Region-based memory management](https://en.wikipedia.org/wiki/Region-based_memory_management) for reference). ## Methods ### `withRegion(unit, callback)` > INFO since: > > [effector 20.11.0](https://changelog.effector.dev/#effector-20-11-0) The method allows to explicitly transfer ownership of all units (including links created with `sample`, `forward`, etc...) defined in the callback to `unit`. As an implication, all the created links will be erased as soon as `clearNode` is called on . #### Formulae ```ts withRegion(unit: Unit | Node, callback: () => void): void ``` #### Arguments 1. `unit`: *Unit* | *Node* — which will serve as "local area" or "region" owning all the units created within the provided callback. Usually a node created by low level `createNode` method is optimal for this case. 2. `callback`: `() => void` — The callback where all the relevant units should be defined. #### Examples ```js import { createNode, createEvent, restore, withRegion, clearNode } from "effector"; const first = createEvent(); const second = createEvent(); const $store = restore(first, ""); const region = createNode(); withRegion(region, () => { // Following links created with `sample` are owned by the provided unit `region` // and will be disposed as soon as `clearNode` is called on `region`. sample({ clock: second, target: first, }); }); $store.watch(console.log); first("hello"); second("world"); clearNode(region); second("will not trigger updates of `$store`"); ``` # API Reference import FeatureCard from "@components/FeatureCard.astro"; import IconReact from "@icons/React.astro"; import IconVue from "@icons/Vue.astro"; import IconSolid from "@icons/Solid.astro"; import IconEffector from "@icons/Effector.astro"; import IconNextJs from "@icons/NextJs.astro"; import MostUsefulMethods from "@components/MostUsefulMethods.astro"; import { MOST\_USEFUL } from "src/navigation"; ## API Reference Short overview of most useful methods and packages provided by Effector. # Protocol @@unitShape ## Protocol `@@unitShape` > INFO: > > Available since [effector-react 22.4.0](https://changelog.effector.dev/#effector-react-22-4-0), effector-solid 0.22.7 Effector provides a way to use units (Stores, Events, Effects) in UI libraries with a special bindings like `effector-react`, `effector-solid`, etc. Normally, they allow binding any shape of units to a UI-framework: ```ts import { createStore } from "effector"; import { useUnit } from "effector-react"; const $value = createStore("Hello!"); const Component = () => { const { value } = useUnit({ value: $value }); return

    {value}

    ; }; ``` But what if you want to create your own library on top of effector with some custom entities? For example, you want to create a router library with a custom `Route` entity, and you want to allow users to use it with `effector-react` bindings: ```ts import { createRoute } from "my-router-library"; import { useUnit } from "effector-react"; const mainPageRoute = createRoute(/* ... */); const Component = () => { const { params } = useUnit(mainPageRoute); return

    {params.name}

    ; }; ``` It is possible with the `@@unitShape` protocol. It allows defining the shape of a unit in the custom entity and then using it in UI libraries. Just add field `@@unitShape` with a function that return shape of units to your entity: ```ts function createRoute(/* ... */) { const $params = createStore(/* ... */); return { "@@unitShape": () => ({ params: $params, }), }; } ``` ### FAQ *** **Q**: How frequently `@@unitShape`-function is called? **A**: As many times as `useUnit` itself is called – it depends on a UI-library. For example, `effector-react` calls it as any other hook – once per component render, but `effector-solid` calls `useUnit` once per component mount. *** **Q**: How can I know what UI-library is used for particular `@@unitShape` call? **A**: You cannot. `@@unitShape` has to be universal for all UI-libraries either has to check what UI-library is used inside by UI-library methods (like `Context` in React or Solid). # Dynamic models ## Dynamic models > ERROR disclaimer: > > At the moment dynamic models are still under development, their API may change over time. This article is for informational purposes only, and we strongly do not recommend using this functionality in production.
    > > **NOT RECOMMENDED FOR PRODUCTION USE**. Currently effector does not support creating units dynamically; units must be defined statically at the module level. If units are created at runtime, a memory leak will occur because units will remain in the graph forever. Although you can try using `withRegion`, `createNode`, and `clearNode`, this requires developer expertise, since these are low-level APIs, and moreover you will have to track the unit lifecycle yourself, which can be a headache. Therefore, for cases where dynamics were needed, key-value stores were used, storing objects where keys were identifiers and values were states, for example: ```ts // model.ts import { createStore, createEvent } from "effector"; import { useStoreMap } from "effector-react"; type Item = { id: string; count: number }; const $items = createStore>({}); const addItem = createEvent(); const removeItem = createEvent(); $items.on(addItem, (state, item) => ({ ...state, [item.id]: item, })); $items.on(removeItem, (state, id) => { const copy = { ...state }; delete copy[id]; return copy; }); ``` In the UI, `useStoreMap` was used to subscribe only to the part that corresponds to `id` to get the data: ```tsx // counter.tsx import { $items, addItem, removeItem } from "./model"; import { useStoreMap, useUnit } from "effector-react"; function Counter({ id }: { id: string }) { const item = useStoreMap({ store: $items, keys: [id], fn: (state, [key]) => state[key], }); const [onAddItem, onRemoveItem] = useUnit([addItem, removeItem]); if (!item) return null; return (
    {item.count}
    ); } ``` Although this approach works, it is not very convenient, especially if the structure is more complex than in this example. Models introduce a new way to work with dynamic states, allowing you to create model instances on the fly that have their own states and logic. ### Setup and working with models Currently models are implemented in a [separate repository](https://github.com/effector/model) and available as a separate package: ```bash npm install @effector/model ``` and also a package for React integration: ```bash npm install @effector/model-react ``` In the root of the [repository](https://github.com/effector/model) you can find the `apps` directory with examples of model usage in applications with near-real functionality. In this article we will just get familiar with the API and what dynamic models look like. ### Model API Dynamic models introduce a set of new APIs: * `keyval` – an operator that creates a collection of model instances, where each element is identified by a key. Dynamic creation and deletion of model instances happens via `keyval`. It can also be used inside another `keyval` for nested structures. It expects a callback that returns an object with the following properties: * `state` – the model state, an object containing stores or another `keyval` model. One of the properties must also serve as the model key * `key` – the model key, i.e. its unique identifier * `api` – an optional object with events or effects for working with the model * `onMount` – an optional event or effect triggered when a model instance is created * `optional` – an optional array of strings representing non-required fields of the model at creation Example: ```ts export const restaurantsList = keyval(() => { const $name = createStore(""); const $description = createStore(""); const $category = createStore([]); const dishesList = keyval(() => { const $name = createStore(""); const $description = createStore(""); const $price = createStore(0); const $additives = createStore([]); return { key: "name", state: { name: $name, description: $description, price: $price, additives: $additives, }, optional: ["additives"], }; }); return { key: "name", state: { name: $name, description: $description, category: $category, dishes: dishesList, }, api: { addDish: dishesList.edit.add, removeDish: dishesList.edit.remove, }, optional: ["category", "dishes"], }; }); ``` Now using `restaurantsList` we can add, update, or remove model instances at runtime. ```ts const addRestaurant = createEvent(); sample({ clock: addRestaurant, fn: () => ({ name: "Starbucks", description: "American corporation and the largest coffeehouse chain in the world", }), target: restaurantsList.edit.add, }); ``` * `lens` – lens is needed to dive inside a `keyval` for working with data. For example, with nested `keyval` we can access data or API from the very top to the very bottom: ```ts const menuItemIdLens = lens(orderKeyval).item(orderId).menuItemId; const foodDescLens = lens(restaurantKeyval).item(restId).menu.item(menuItemIdLens).description; ``` > INFO lens api: > > At the moment the lens API is still being refined and may differ from what is shown in the repository examples. In addition to the main effector package, there is also an API for effector-react to conveniently work with models in React: * `useEntityList(keyval, View)` – hook that takes `keyval` as the first argument and a component as the second. Iterates over all keys in the collection and for each creates an `EntityProvider`, passing `View` into it. Simply put, it’s a way to render a list and later work with other hooks without passing `id`. * `useEntityItem(keyval, key?)` – returns an entity by `id` in a `keyval` collection. If `key` is explicitly provided, it searches for the element by this key, if not provided, it tries to get it from the nearest `EntityProvider`. * `useItemApi(keyval, key?)` – returns the entity API object for working with it. * `useEditItemField(keyval, key?)` – returns an object with functions for updating each model field. If `key` is explicitly provided, it searches for the element by this key, if not provided, it tries to get it from the nearest `EntityProvider`. * `useEditKeyval(keyval)` – returns an object of methods for modifying the model, such as add, delete, or update. ```ts const { add, map, remove, replaceAll, set, update } = useEditKeyval(ordersList); ``` ### Related API and docs * **API** * clearNode — Method for destroying units * withRegion — Method to set region for units * **Articles** * Unit initialization # Events import SideBySide from "@components/SideBySide/SideBySide.astro"; ## Events Although effector has three main concepts — events, stores, and effects — it is events that can be called the foundation for how your application works. The entire frontend is built atop an event-driven model, and you have most likely already worked with events in JavaScript, for example via `addEventListener` or React handlers like `onClick`. They are all events we can subscribe to, and effector lets us create our own event with createEvent and subscribe to it: ```ts data-height="full" let counter = 10; document.addEventListener("click", () => { counter = 13; }); ``` ```ts data-height="full" import { createEvent, createStore, sample } from "effector"; const $counter = createStore(10); const click = createEvent(); sample({ clock: click, fn: () => 13, target: $counter, }); ``` > INFO Effector events: > > Events in effector are not the same as DOM events; this example only demonstrates a similar concept. ### Why we need events All effector code is built on top of events. For example, if you pass a store to the clock argument of sample, that sample will be triggered whenever the updates event of the store fires: ```ts "clock: $someStore" // this code is equivalent to the example on the right import { createStore, sample } from "effector"; const $someStore = createStore(); sample({ clock: $someStore, // ... }); ``` ```ts "clock: $someStore.updates" // this code is equivalent to the example on the left import { createStore, sample } from "effector"; const $someStore = createStore(); sample({ clock: $someStore.updates, // ... }); ``` An event by itself simply states the fact that something has happened: a button click, data refresh, notification display, and so on. Events pass information along and notify their subscribers that they have been triggered. To make it easier to reason about your code, treat everything the user can interact with and the inner parts of your business logic as events. Besides UI-oriented events like `buttonClicked` or `inputChanged`, you can also have events tied to data updates `valueUpdated` or exceptional situations `errorOccurred`. For example, effects have built-in events done and fail that run when an effect finishes successfully or throws an error. Stores have the updates event that is triggered whenever the store changes. Events also help you keep the code loosely coupled, which simplifies maintenance and testing. > TIP Why events matter: > > On the How to think in the effector paradigm page we dig deeper into why events are important and the benefits they bring. #### How to use events You can create events with createEvent and subscribe to them with sample: ```ts import { sample, createEvent } from "effector"; const event = createEvent(); sample({ clock: event, // ... }); ``` > TIP What is sample: > > If you are not familiar with sample, in the examples above it subscribes to the event passed via clock and runs the described chain of logic whenever the event triggers. You can learn more on the Unit composition page. With events you can update stores, trigger effects, or fire other events: ```ts import { sample, createStore, createEvent } from "effector"; const $clicksCount = createStore(0); const userClicked = createEvent(); // update the counter store sample({ clock: userClicked, source: $clicksCount, fn: (clicksCount) => clicksCount + 1, target: $clicksCount, }); userClicked(); ``` ```ts import { createEvent, createEffect, sample } from "effector"; const startFetch = createEvent(); const fetchFx = createEffect((url: string) => { // ... }); // trigger an effect sample({ clock: startFetch, target: fetchFx, }); startFetch("/fake-api/users"); ``` An important characteristic of events is that they can accept only one argument; additional arguments are ignored. Use an object if you need to pass multiple values: ```ts const event = createEvent<{ a: number; b: string }>(); event({ a: 1, b: "string", }); ``` ### Event calling You can trigger an event in two ways: imperatively or declaratively. The **imperative** approach is a familiar one, where you call an event as a function (`event()`). This is how you will typically fire events from the UI by passing a handler to `onClick`, `onChange`, and so on: ```tsx "const click = useUnit(clickHappened);" "click()" "useUnit" import { createEvent } from "effector"; // you can use any framework and its integration package import { useUnit } from "effector-react"; const clickHappened = createEvent(); // view.tsx const Component = () => { const click = useUnit(clickHappened); return (
    ); }; ``` > INFO Events and frameworks: > > To call an event in UI code that uses a framework you need to access it through the `useUnit` hook. A **declarative** call happens when you connect one event to another with sample and the target argument. The event passed into target will run whenever the clock event fires: ```ts import { createEvent, sample } from "effector"; const firstTriggered = createEvent(); const secondTriggered = createEvent(); sample({ clock: firstTriggered, target: secondTriggered, }); ``` > TIP Can I call sample imperatively?: > > The declarative way is the only correct approach when you work with `sample`. However, you can still call events imperatively through or inside an effect body. ### Related API and docs * **API** * Event API — description of events, their methods, and properties * createEvent — create a new event * sample — the key operator for connecting units * Store API — description of stores, their methods, and properties * **Articles** * How to think in the effector paradigm and why events matter * Typing events and other units * How to glue units together with # Splitting Data Streams with split import { Image } from "astro> ASSETS: "; import Tabs from "@components/Tabs/Tabs.astro"; import TabItem from "@components/Tabs/TabItem.astro"; import ThemeImage from "@components/ThemeImage.astro"; ## Splitting Data Streams with `split` The `split` method is designed to divide logic into multiple data streams. For example, you might need to route data differently depending on its content, much like a railway switch that directs trains to different tracks: * If a form is filled incorrectly — display an error. * If everything is correct — send a request. > INFO Condition Checking Order: > > Conditions in `split` are checked sequentially from top to bottom. Once a condition matches, subsequent ones are not evaluated. Keep this in mind when crafting your conditions. ### Basic Usage of `split` Let's look at a simple example — processing messages of different types: ```ts import { createEvent, split } from "effector"; const updateUserStatus = createEvent(); const { activeUserUpdated, idleUserUpdated, inactiveUserUpdated } = split(updateUserStatus, { activeUserUpdated: (userStatus) => userStatus === "active", idleUserUpdated: (userStatus) => userStatus === "idle", inactiveUserUpdated: (userStatus) => userStatus === "inactive", }); ``` The logic here is straightforward. When the `updateUserStatus` event is triggered, it enters `split`, which evaluates each condition from top to bottom until a match is found, then triggers the corresponding event in `effector`. Each condition is defined by a predicate — a function returning `true` or `false`. You might wonder, "Why use this when I could handle conditions with `if/else` in the UI?" The answer lies in Effector's philosophy of **separating business logic** from the UI. > TIP: > > Think of `split` as a reactive `switch` for units. ### Default Case When using `split`, there might be situations where no conditions match. For such cases, there's a special default case: `__`. Here's the same example as before, now including a default case: ```ts import { createEvent, split } from "effector"; const updateUserStatus = createEvent(); const { activeUserUpdated, idleUserUpdated, inactiveUserUpdated, __ } = split(updateUserStatus, { activeUserUpdated: (userStatus) => userStatus === "active", idleUserUpdated: (userStatus) => userStatus === "idle", inactiveUserUpdated: (userStatus) => userStatus === "inactive", }); __.watch((defaultStatus) => console.log("default case with status:", defaultStatus)); activeUserUpdated.watch(() => console.log("active user")); updateUserStatus("whatever"); updateUserStatus("active"); updateUserStatus("default case"); // Console output: // default case with status: whatever // active user // default case with status: default case ``` > INFO Default Handling: > > If no conditions match, the default case `__` will be triggered. ### Short Form The `split` method supports multiple usage patterns based on your needs. The shortest usage involves passing a unit as the first argument serving as a trigger and an object with cases as the second argument. Let's look at an example with GitHub's "Star" and "Watch" buttons: ```ts import { createStore, createEvent, split } from "effector"; type Repo = { // ... other properties isStarred: boolean; isWatched: boolean; }; const toggleStar = createEvent(); const toggleWatch = createEvent(); const $repo = createStore(null) .on(toggleStar, (repo) => ({ ...repo, isStarred: !repo.isStarred, })) .on(toggleWatch, (repo) => ({ ...repo, isWatched: !repo.isWatched })); const { starredRepo, unstarredRepo, __ } = split($repo, { starredRepo: (repo) => repo.isStarred, unstarredRepo: (repo) => !repo.isStarred, }); // Debug default case __.watch((repo) => console.log("[split toggleStar] Default case triggered with value ", repo)); // Somewhere in the app toggleStar(); ``` This usage returns an object with derived events, which can trigger reactive chains of actions. > TIP: > > Use this pattern when: > > * There are no dependencies on external data (e.g., stores). > * You need simple, readable code. ### Expanded Form Using the `split` method in this variation doesn't return any value but provides several new capabilities: 1. You can depend on external data, such as stores, using the `match` parameter. 2. Trigger multiple units when a case matches by passing an array. 3. Add a data source using `source` and a trigger using `clock`. For example, imagine a scenario where your application has two modes: `user` and `admin`. When an event is triggered, different actions occur depending on whether the mode is `user` or `admin`: ```ts import { createStore, createEvent, createEffect, split } from "effector"; const adminActionFx = createEffect(); const secondAdminActionFx = createEffect(); const userActionFx = createEffect(); const defaultActionFx = createEffect(); // UI event const buttonClicked = createEvent(); // Current application mode const $appMode = createStore<"admin" | "user">("user"); // Different actions for different modes split({ source: buttonClicked, match: $appMode, // Logic depends on the current mode cases: { admin: [adminActionFx, secondAdminActionFx], user: userActionFx, __: defaultActionFx, }, }); // Clicking the same button performs different actions // depending on the application mode buttonClicked(); // -> "Performing user action" (when $appMode = 'user') // -> "Performing admin action" (when $appMode = 'admin') ``` Additionally, you can include a `clock` property that works like in sample, acting as a trigger, while `source` provides the data to be passed into the respective case. Here's an extended example: ```ts // Extending the previous code const adminActionFx = createEffect((currentUser) => { // ... }); const secondAdminActionFx = createEffect((currentUser) => { // ... }); // Adding a new store const $currentUser = createStore({ id: 1, name: "Donald", }); const $appMode = createStore<"admin" | "user">("user"); split({ clock: buttonClicked, // Passing the new store as a data source source: $currentUser, match: $appMode, cases: { admin: [adminActionFx, secondAdminActionFx], user: userActionFx, __: defaultActionFx, }, }); ``` > WARNING Default Case: > > If you need a default case, you must explicitly define it in the `cases` object, otherwise, it won' t be processed! In this scenario, the logic for handling cases is determined at runtime based on `$appMode`, unlike the earlier example where it was defined during `split` creation. > INFO Usage Notes: > > When using `match`, it can accept units, functions, or objects with specific constraints: > > * **Store**: If using a store, **it must store a string value**. > * **Function**: If passing a function, **it must return a string value and be pure**. > * **Object with stores**: If passing an object of stores, **each store must hold a boolean value**. > * **Object with functions**: If passing an object of functions, **each function must return a boolean value and be pure**. #### `match` as a Store When `match` is a store, the value in the store is used as a key to select the corresponding case: ```ts const $currentTab = createStore("home"); split({ source: pageNavigated, match: $currentTab, cases: { home: loadHomeDataFx, profile: loadProfileDataFx, settings: loadSettingsDataFx, }, }); ``` #### `match` as a Function When using a function for `match`, it must return a string to be used as the case key: ```ts const userActionRequested = createEvent<{ type: string; payload: any }>(); split({ source: userActionRequested, match: (action) => action.type, // The function returns a string cases: { update: updateUserDataFx, delete: deleteUserDataFx, create: createUserDataFx, }, }); ``` #### `match` as an Object with Stores When `match` is an object of stores, each store must hold a boolean value. The case whose store contains true will execute: ```ts const $isAdmin = createStore(false); const $isModerator = createStore(false); split({ source: postCreated, match: { admin: $isAdmin, moderator: $isModerator, }, cases: { admin: createAdminPostFx, moderator: createModeratorPostFx, __: createUserPostFx, }, }); ``` #### `match` as an Object with Functions If using an object of functions, each function must return a boolean. The first case with a `true` function will execute: ```ts split({ source: paymentReceived, match: { lowAmount: ({ amount }) => amount < 100, mediumAmount: ({ amount }) => amount >= 100 && amount < 1000, highAmount: ({ amount }) => amount >= 1000, }, cases: { lowAmount: processLowPaymentFx, mediumAmount: processMediumPaymentFx, highAmount: processHighPaymentFx, }, }); ``` > WARNING Attention: > > Ensure your conditions in `match` are mutually exclusive. Overlapping conditions may cause unexpected behavior. Always verify the logic to avoid conflicts. ### Practical Examples #### Handling forms with split ```ts const showFormErrorsFx = createEffect(() => { // Logic to display errors }); const submitFormFx = createEffect(() => { // Logic to submit the form }); const submitForm = createEvent(); const $form = createStore({ name: "", email: "", age: 0, }).on(submitForm, (_, submittedForm) => ({ ...submittedForm })); // Separate store for errors const $formErrors = createStore({ name: "", email: "", age: "", }).reset(submitForm); // Validate fields and collect errors sample({ clock: submitForm, source: $form, fn: (form) => ({ name: !form.name.trim() ? "Name is required" : "", email: !isValidEmail(form.email) ? "Invalid email" : "", age: form.age < 18 ? "Age must be 18+" : "", }), target: $formErrors, }); // Use split for routing based on validation results split({ source: $formErrors, match: { hasErrors: (errors) => Object.values(errors).some((error) => error !== ""), }, cases: { hasErrors: showFormErrorsFx, __: submitFormFx, }, }); ``` Explanation: Two effects are created: one to display errors and one to submit the form. Two stores are defined: `$form` for form data and `$formErrors` for errors. On form submission `submitForm`, two things happen: 1. Form data is updated in the `$form` store. 2. All fields are validated using `sample`, and errors are stored in `$formErrors`. The `split` method determines the next step: * If any field has an error – ❌ display the errors. * If all fields are valid – ✅ submit the form. # State management import Tabs from "@components/Tabs/Tabs.astro"; import TabItem from "@components/Tabs/TabItem.astro"; import SideBySide from "@components/SideBySide/SideBySide.astro"; ## State management All state management is done using stores, and the key feature is that stores do not have the usual `setState`. A store updates reactively when the event it is subscribed to is triggered, for example: ```ts import { createStore, createEvent } from "effector"; const $counter = createStore(0); const incremented = createEvent(); // when incremented triggered, increase the counter by 1 $counter.on(incremented, (counterValue) => counterValue + 1); incremented(); // $counter = 1 incremented(); // $counter = 2 ``` If you are not familiar with events yet, just think of them as a trigger for updating the store. You can learn more about events on the events page, as well as how to think in the effector paradigm and why events matter. > INFO Data Immutability: > > If you store a [reference type](https://learn.javascript.ru/reference-type), such as an array or an object, in a store, then to update such a store you can either use immer or first create a new instance of that type: > > > > > > ```ts wrap data-border="good" data-height="full" "const updatedUsers = [...users];" "const updatedUser = { ...user };" > // ✅ all good > > // update array > $users.on(userAdded, (users, newUser) => { > const updatedUsers = [...users]; > updatedUsers.push(newUser); > return updatedUsers; > }); > > // update object > $user.on(nameChanged, (user, newName) => { > const updatedUser = { ...user }; > updatedUser.name = newName; > return updatedUser; > }); > ``` > > > > > > ```ts wrap data-border="bad" data-height="full" > // ❌ this is bad > > $users.on(userAdded, (users, newUser) => { > users.push(newUser); // mutate array > return users; > }); > > $user.on(nameChanged, (user, newName) => { > user.name = newName; // mutate object > return user; > }); > ``` > > > > ### Store creation Creating a store is done using the createStore method: ```ts import { createStore } from "effector"; // creating a store with an initial value const $counter = createStore(0); // and with explicit typing const $user = createStore<{ name: "Bob"; age: 25 } | null>(null); const $posts = createStore([]); ``` > TIP Naming stores: > > The effector team suggests using the , as it improves code readability and IDE autocompletion. ### Reading values As you already know, effector is a reactive state manager, and a store is a reactive unit — reactivity is not created in a magical way. If you try to just use a store, for example: ```ts import { createStore } from "effector"; const $counter = createStore(0); console.log($counter); ``` You will see an obscure object with a bunch of properties, which effector needs for correct operation, but not the current value. To get the current value of a store, there are several ways: 1. Most likely you are also using some framework like [React](https://react.dev/), [Vue](https://vuejs.org/), or [Solid](https://docs.solidjs.com/), and in that case you need an adapter for this framework: effector-react, effector-vue, or effector-solid. Each of these packages provides the `useUnit` hook to get data from a store and subscribe to its changes. When working with UI, this is the only correct way to read data: ```ts "useUnit" import { useUnit } from 'effector-react' import { $counter } from './model.js' const Counter = () => { const counter = useUnit($counter) return
    {counter}
    } ```
    ```html "useUnit" ``` ```ts "useUnit" import { useUnit } from 'effector-solid' import { $counter } from './model.js' const Counter = () => { const counter = useUnit($counter) return
    {counter()}
    } ```
    2. Since for building your logic outside the UI you may also need store data, you can use the sample method and pass the store to `source`, for example: ```ts import { createStore, createEvent, sample } from "effector"; const $counter = createStore(0); const incremented = createEvent(); sample({ clock: incremented, source: $counter, fn: (counter) => { console.log("Counter value:", counter); }, }); incremented(); ``` A bit later we will also discuss the with stores. 3. You can subscribe to store changes using watch, however this is mainly used for debugging or for some custom integrations: ```ts $counter.watch((counter) => { console.log("Counter changed:", counter); }); ``` 4. The getState() method is generally used only for working with low-level APIs or integrations. Try not to use it in your code, as it may lead to race conditions: ```ts console.log($counter.getState()); // 0 ``` > WARNING Why not use getState?: > > For effector to work correctly with reactivity, it needs to build connections between units so that the data is always up to date. In the case of .getState(), we essentially break this system and take the data from the outside. ### Store updates As mentioned earlier, state updates happen through events. A store can subscribe to events using the .on method — good for primitive reactions, or the sample operator — which allows updating a store based on another store or filtering updates. > INFO What is sample?: > > The sample method is an operator for creating connections between units. With it, you can trigger events or effects, as well as write new values into stores. Its algorithm is simple: > > ```ts > const trigger = createEvent(); > const log = createEvent(); > > sample({ > clock: trigger, // 1. when trigger fires > source: $counter, // 2. take the value from $counter > filter: (counter) => counter % 2 === 0, // 3. if the value is even > fn: (counter) => "Counter is even: " + counter, // 4. transform it > target: log, // 5. call and pass to log > }); > ``` #### Using `.on` With .on, we can update a store in a primitive way: event triggered → call the callback → update the store with the returned value: ```ts import { createStore, createEvent } from "effector"; const $counter = createStore(0); const incrementedBy = createEvent(); const decrementedBy = createEvent(); $counter.on(incrementedBy, (counterValue, delta) => counterValue + delta); $counter.on(decrementedBy, (counterValue, delta) => counterValue - delta); incrementedBy(11); // 0+11=11 incrementedBy(39); // 11+39=50 decrementedBy(25); // 50-25=25 ``` #### Using `sample` With the sample method, we can update a store in a primitive way: ```ts import { sample } from "effector"; sample({ clock: incrementedBy, // when incrementedBy is triggered source: $counter, // take data from $counter fn: (counter, delta) => counter + delta, // call fn target: $counter, // update $counter with the value returned from fn }); sample({ clock: decrementedBy, // when decrementedBy is triggered source: $counter, // take data from $counter fn: (counter, delta) => counter - delta, // call fn target: $counter, // update $counter with the value returned from fn }); ``` At the same time, we also have more flexible ways — for example, updating a store only when **another store** has the required value. For example, perform a search only when `$isSearchEnabled` is `true`: ```ts import { createStore, createEvent, sample } from "effector"; const $isSearchEnabled = createStore(false); const $searchQuery = createStore(""); const $searchResults = createStore([]); const searchTriggered = createEvent(); sample({ clock: searchTriggered, // when searchTriggered is triggered source: $searchQuery, // take data from $searchQuery filter: $isSearchEnabled, // continue only if search is enabled fn: (query) => { // simulate a search return ["result1", "result2"].filter((item) => item.includes(query)); }, target: $searchResults, // update $searchResults with the value returned from fn }); ``` Note that when passing a store into `target`, its previous value will be fully replaced with the value returned from `fn`. #### Updating from multiple events A store is not limited to a single event subscription — you can subscribe to as many events as needed, and multiple stores can subscribe to the same event: ```ts "categoryChanged" import { createEvent, createStore, sample } from "effector"; const $lastUsedFilter = createStore(null); const $filters = createStore({ category: "all", searchQuery: "", }); const categoryChanged = createEvent(); const searchQueryChanged = createEvent(); // two different stores subscribing to the same event $lastUsedFilter.on(categoryChanged, (_, category) => category); sample({ clock: categoryChanged, source: $filters, fn: (filters, category) => ({ // following immutability principles ...filters, category, }), // the result of fn will replace the previous value in $filters target: $filters, }); // store subscribing to two different events: searchQueryChanged and categoryChanged sample({ clock: searchQueryChanged, source: $filters, fn: (filters, searchQuery) => ({ // following immutability principles ...filters, searchQuery, }), // the result of fn will replace the previous value in $filters target: $filters, }); ``` Here we subscribed two stores to the same `categoryChanged` event, and also subscribed the `$filters` store to another event `searchQueryChanged`. ### Derived stores A derived store is computed **based on other stores** and **automatically updates** when those stores change. Imagine we have the following store: ```ts import { createStore } from "effector"; const $author = createStore({ name: "Hanz Zimmer", songs: [ { title: "Time", likes: 123 }, { title: "Cornfield Chase", likes: 97 }, { title: "Dream is Collapsing", likes: 33 }, ], }); ``` And we want to display the total number of likes, as well as the number of songs for this author. Of course, we could just use this store in the UI with the `useUnit` hook and calculate those values directly in the component. But this is not the right approach, because we would be mixing logic inside the component and spreading it throughout the application, making the code harder to maintain in the future. And if we wanted to reuse the same logic elsewhere, we’d have to duplicate the code.
    In this case, the correct approach is to create derived stores based on `$author` using the combine method: ```ts ins={13,15-17} "combine" import { createStore, combine } from "effector"; const $author = createStore({ name: "Hanz Zimmer", songs: [ { title: "Time", likes: 123 }, { title: "Cornfield Chase", likes: 97 }, { title: "Dream is Collapsing", likes: 33 }, ], }); // total number of songs const $totalSongsCount = combine($author, (author) => author.songs.length); // total number of likes const $totalLikesCount = combine($author, (author) => author.songs.reduce((acc, song) => acc + song.likes, 0), ); ``` Each of these derived stores will automatically update whenever the original `$author` store changes. > WARNING Important about derived stores!: > > Derived stores automatically update when the source stores change. They cannot be passed as a `target` in `sample` or subscribed to with `.on`. At the same time, there can be as many source stores as needed, which allows you, for example, to compute the current application state: ```ts "$isLoading, $isSuccess, $error" import { combine, createStore } from "effector"; const $isLoading = createStore(false); const $isSuccess = createStore(false); const $error = createStore(null); const $isAppReady = combine($isLoading, $isSuccess, $error, (isLoading, isSuccess, error) => { return !isLoading && isSuccess && !error; }); ``` ### `undefined` values If you try to use a store value as `undefined` or put this value into a store: ```ts "return undefined;" const $store = createStore(0).on(event, (_, newValue) => { if (newValue % 2 === 0) { return undefined; } return newValue; }); ``` you will encounter an error in the console: ```console store: undefined is used to skip updates. To allow undefined as a value provide explicit { skipVoid: false } option ``` By default, returning `undefined` acts as a command "nothing happened, skip this update". If you really need to use `undefined` as a valid value, you must explicitly specify it with the `skipVoid: false` option when creating a store: ```ts "skipVoid: false" import { createStore } from "effector"; const $store = createStore(0, { skipVoid: false, }); ``` > INFO The future of undefined: > > In upcoming versions this behavior will be changed. As practice shows, it’s usually better to just return the previous store value to avoid updating it. ### Related API and docs * **API** * createStore — Method for creating a store * Store — Description of a store and its methods * **Articles** * Core concepts * Working with events # TypeScript import Tabs from "@components/Tabs/Tabs.astro"; import TabItem from "@components/Tabs/TabItem.astro"; ## TypeScript in effector Effector provides first-class TypeScript support out of the box, giving you reliable typing and excellent development experience when working with the library. In this section, we'll look at both basic typing concepts and advanced techniques for working with types in effector. ### Typing Events Events in Effector can be typed by passing a type to the generic function. However, if nothing is passed, the event will have the type `EventCallable`: ```ts import { createEvent } from "effector"; // Event without parameters const clicked = createEvent(); // EventCallable // Event with parameter const userNameChanged = createEvent(); // EventCallable // Event with complex parameter const formSubmitted = createEvent<{ username: string; password: string; }>(); // EventCallable<{ username: string; password: string; }> ``` #### Event Types In Effector, events can have several types, where `T` is the stored value type: * `EventCallable` - an event that can be called. * `Event` - a derived event that cannot be called manually. #### Typing Event Methods ##### event.prepend To add types to events created using event.prepend, you need to add the type either in the prepend function argument or as a generic: ```ts const message = createEvent(); const userMessage = message.prepend((text: string) => text); // userMessage has type EventCallable const warningMessage = message.prepend((warnMessage) => warnMessage); // warningMessage has type EventCallable ``` ### Typing Stores Stores can also be typed by passing a type to the generic function, or by specifying a default value during initialization, then TypeScript will infer the type from this value: ```ts import { createStore } from "effector"; // Basic store with primitive value // StoreWritable const $counter = createStore(0); // Store with complex object type interface User { id: number; name: string; role: "admin" | "user"; } // StoreWritable const $user = createStore({ id: 1, name: "Bob", role: "user", }); // Store const $userNameAndRole = $user.map((user) => `User name and role: ${user.name} and ${user.role}`); ``` #### Store Types In Effector, there are two types of stores, where T is the stored value type: * `Store` - derived store type that cannot have new data written to it. * `StoreWritable` - store type that can have new data written using on or sample. ### Typing Effects In normal usage, TypeScript will infer types based on the function's return result and its arguments. However, `createEffect` supports typing of input parameters, return result, and errors through generics: ```ts import { createEffect } from "effector"; // Base effect // Effect const fetchUserFx = createEffect(async (userId: string) => { const response = await fetch(`/api/users/${userId}`); const result = await response.json(); return result as User; }); ``` ```ts import { createEffect } from "effector"; // Base effect // Effect const fetchUserFx = createEffect(async (userId) => { const response = await fetch(`/api/users/${userId}`); const result = await response.json(); return result; }); ``` #### Typing Handler Function Outside Effect If the handler function is defined outside the effect, you'll need to pass that function's type: ```ts const sendMessage = async (params: { text: string }) => { // ... return "ok"; }; const sendMessageFx = createEffect(sendMessage); // => Effect<{text: string}, string, AxiosError> ``` #### Custom Effect Errors Some code may only throw certain types of exceptions. In effects, the third generic `Fail` is used to describe error types: ```ts // Define API error types interface ApiError { code: number; message: string; } // Create typed effect const fetchUserFx = createEffect(async (userId) => { const response = await fetch(`/api/users/${userId}`); if (!response.ok) { throw { code: response.status, message: "Failed to fetch user", } as ApiError; } return response.json(); }); ``` ### Typing Methods #### `sample` ##### Typing `filter` If you need to get a specific type, you'll need to manually specify the expected type, which can be done using [type predicates](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates): ```ts type UserMessage = { kind: "user"; text: string }; type WarnMessage = { kind: "warn"; warn: string }; const message = createEvent(); const userMessage = createEvent(); sample({ clock: message, filter: (msg): msg is UserMessage => msg.kind === "user", target: userMessage, }); ``` If you need to check for data existence in `filter`, you can simply pass `Boolean`: ```ts import { createEvent, createStore, sample } from "effector"; interface User { id: string; name: string; email: string; } // Events const formSubmitted = createEvent(); const userDataSaved = createEvent(); // States const $currentUser = createStore(null); // On form submit, send data only if user exists sample({ clock: formSubmitted, source: $currentUser, filter: Boolean, // filter out null target: userDataSaved, }); // Now userDataSaved will only receive existing user data ``` ##### Typing `filter` and `fn` As mentioned above, using type predicates in `filter` will work correctly and the correct type will reach the `target`. However, this mechanism won't work as needed when using `filter` and `fn` together. In this case, you'll need to manually specify the data type of `filter` parameters and add type predicates. This happens because TypeScript cannot correctly infer the type in `fn` after `filter` if the type isn't explicitly specified. This is a limitation of TypeScript's type system. ```ts type UserMessage = { kind: "user"; text: string }; type WarnMessage = { kind: "warn"; warn: string }; type Message = UserMessage | WarnMessage; const message = createEvent(); const userText = createEvent(); sample({ clock: message, filter: (msg: Message): msg is UserMessage => msg.kind === "user", fn: (msg) => msg.text, target: userText, }); // userMessage has type Event ``` > TIP It got smarter!: > > Starting from TypeScript version >= 5.5, you don't need to write type predicates, just specify the argument type and TypeScript will understand what needs to be inferred: > `filter: (msg: Message) => msg.kind === "user"` #### attach To allow TypeScript to infer the types of the created effect, you can add a type to the first argument of `mapParams`, which will become the `Params` generic of the result: ```ts const sendTextFx = createEffect<{ message: string }, "ok">(() => { // ... return "ok"; }); const sendWarningFx = attach({ effect: sendTextFx, mapParams: (warningMessage: string) => ({ message: warningMessage }), }); // sendWarningFx has type Effect<{message: string}, 'ok'> ``` #### split ```ts type UserMessage = { kind: "user"; text: string }; type WarnMessage = { kind: "warn"; warn: string }; const message = createEvent(); const { userMessage, warnMessage } = split(message, { userMessage: (msg): msg is UserMessage => msg.kind === "user", warnMessage: (msg): msg is WarnMessage => msg.kind === "warn", }); // userMessage имеет тип Event // warnMessage имеет тип Event ``` ```ts type UserMessage = { kind: "user"; text: string }; type WarnMessage = { kind: "warn"; warn: string }; const message = createEvent(); const { userMessage, warnMessage } = split(message, { userMessage: (msg) => msg.kind === "user", warnMessage: (msg) => msg.kind === "warn", }); // userMessage имеет тип Event // warnMessage имеет тип Event ``` #### `createApi` To allow TypeScript to infer types of created events, adding a type to second argument of given reducers ```typescript const $count = createStore(0); const { add, sub } = createApi($count, { add: (x, add: number) => x + add, sub: (x, sub: number) => x - sub, }); // add has type Event // sub has type Event ``` #### `is` `is` methods can help to infer a unit type (thereby `is` methods acts as [TypeScript type guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types)) which can help to write strongly-typed helper functions ```typescript export function getUnitType(unit: unknown) { if (is.event(unit)) { // here unit has Event type return "event"; } if (is.effect(unit)) { // here unit has Effect type return "effect"; } if (is.store(unit)) { // here unit has Store type return "store"; } } ``` #### `merge` When we wanna merge events we can get their union types: ```ts import { createEvent, merge } from "effector"; const firstEvent = createEvent(); const secondEvent = createEvent(); const merged = merge([firstEvent, secondEvent]); // Event // You can also combine events with the same types const buttonClicked = createEvent(); const linkClicked = createEvent(); const anyClick = merge([buttonClicked, linkClicked]); // Event ``` `merge` accepts generic, where you can use what type do you expect from events: ```ts import { createEvent, merge } from "effector"; const firstEvent = createEvent(); const secondEvent = createEvent(); const merged = merge([firstEvent, secondEvent]); // ^ // Type 'EventCallable' is not assignable to type 'Unit'. ``` ### Type Utilities Effector provides a set of utility types for working with unit types: #### UnitValue The `UnitValue` type is used to extract the data type from units: ```ts import { UnitValue, createEffect, createStore, createEvent } from "effector"; const event = createEvent<{ id: string; name?: string } | { id: string }>(); type UnitEventType = UnitValue; // {id: string; name?: string | undefined} | {id: string} const $store = createStore([false, true]); type UnitStoreType = UnitValue; // boolean[] const effect = createEffect<{ token: string }, any, string>(() => {}); type UnitEffectType = UnitValue; // {token: string} const scope = fork(); type UnitScopeType = UnitValue; // any ``` #### StoreValue `StoreValue` is essentially similar to `UnitValue`, but works only with stores: ```ts import { createStore, StoreValue } from "effector"; const $store = createStore(true); type StoreValueType = StoreValue; // boolean ``` #### EventPayload Extracts the data type from events. Similar to `UnitValue`, but only for events: ```ts import { createEvent, EventPayload } from "effector"; const event = createEvent<{ id: string }>(); type EventPayloadType = EventPayload; // {id: string} ``` #### EffectParams Takes an effect type as a generic parameter, allows getting the parameter type of an effect. ```ts import { createEffect, EffectParams } from "effector"; const fx = createEffect< { id: string }, { name: string; isAdmin: boolean }, { statusText: string; status: number } >(() => { // ... return { name: "Alice", isAdmin: false }; }); type EffectParamsType = EffectParams; // {id: string} ``` #### EffectResult Takes an effect type as a generic parameter, allows getting the return value type of an effect. ```ts import { createEffect, EffectResult } from "effector"; const fx = createEffect< { id: string }, { name: string; isAdmin: boolean }, { statusText: string; status: number } >(() => ({ name: "Alice", isAdmin: false })); type EffectResultType = EffectResult; // {name: string; isAdmin: boolean} ``` #### EffectError Takes an effect type as a generic parameter, allows getting the error type of an effect. ```ts import { createEffect, EffectError } from "effector"; const fx = createEffect< { id: string }, { name: string; isAdmin: boolean }, { statusText: string; status: number } >(() => ({ name: "Alice", isAdmin: false })); type EffectErrorType = EffectError; // {statusText: string; status: number} ``` # Unit composition import SideBySide from "@components/SideBySide/SideBySide.astro"; import Tabs from "@components/Tabs/Tabs.astro"; import TabItem from "@components/Tabs/TabItem.astro"; ## Unit composition If we consider each unit as a building block of our application, then for full functionality we need to somehow glue these blocks together. For example, when a form submission event occurs, validate the form data, if everything is correct, call an effect to send the data, and also update our store. In other words, build connections between units. To implement this, you need to use the sample operator or [`createAction`](https://github.com/AlexeyDuybo/effector-action?tab=readme-ov-file#createaction). Conceptually, both operators perform the same work, however, they have a difference: sample is a declarative operator, while [`createAction`](https://github.com/AlexeyDuybo/effector-action?tab=readme-ov-file#createaction) is more imperative, allowing you to describe logic in a more familiar style. ```ts data-height="full" // sample import { sample, createEvent } from "effector"; const sendButtonClicked = createEvent(); sample({ clock: sendButtonClicked, source: $formData, filter: (form) => form.username.length > 0 && form.age >= 18, fn: (form) => ({ ...form, timestamp: Date.now(), }), target: [sendFormFx, formSubmitted], }); ``` ```ts data-height="full" // createAction import { createAction } from "effector-action"; import { createEvent } from "effector"; const sendButtonClicked = createEvent(); createAction(sendButtonClicked, { source: $formData, target: { sendForm: sendFormFx, formSubmitted, }, fn: (target, form) => { if (form.username.length > 0 && form.age >= 18) { const updatedForm = { ...form, timestamp: Date.now(), }; target.sendForm(updatedForm); target.formSubmitted(); } }, }); ``` Both operators trigger when the `sendButtonClicked` event is called, then take data from source, and then: * In sample, separate parameters are used: filter for checking conditions, fn for data transformation, and target for calling units. * In [`createAction`](https://github.com/AlexeyDuybo/effector-action?tab=readme-ov-file#createaction), all logic is in a single `fn`, where you can use regular `if` statements for conditions and explicitly call the needed `target`. > INFO action: > > `createAction` is an operator from the external package [`effector-action`](https://github.com/AlexeyDuybo/effector-action), which will move to the effector core package in the nearest major release. Additionally, you need to install the [patronum](https://patronum.effector.dev) package. > > > > > ```bash > npm install effector-action patronum > ``` > > > > > ```bash > yarn install effector-action patronum > ``` > > > > > ```bash > pnpm install effector-action patronum > ``` > > > ### Basic usage Let's look at a basic example from the beginning of the article: we want to validate form data when a form submission event occurs, call an effect to send the data if everything is correct, and also update our store. Let's first look at what units we need: * We need an event `submitForm` for form submission * Several stores - `$formData` to store form data and `$formSubmitted` for the form submission status * And an effect `sendFormFx` to send data to the server > TIP Why not call the effect directly from UI?: > > On the How to think in the effector paradigm page, we explain why you should create events rather than just calling effects directly from UI. ```ts import { createEvent, createStore, sample, createEffect } from "effector"; const submitForm = createEvent(); const $formData = createStore({ username: "", age: 0 }); const $formSubmitted = createStore(false); const sendFormFx = createEffect((formData: { username: string; age: number }) => { // some logic to send data to the server }); ``` In the UI, we will call the `submitForm` event when the user clicks the submit button. It remains to build connections between units: ```ts ins={12-30} import { createEvent, createStore, createEffect, sample } from "effector"; const submitForm = createEvent(); const $formData = createStore({ username: "", age: 0 }); const $formSubmitted = createStore(false); const sendFormFx = createEffect((formData: { username: string; age: number }) => { // some logic to send data to the server }); sample({ clock: submitForm, source: $formData, filter: (form) => form.age >= 18 && form.username.length > 0, target: sendFormFx, }); sample({ clock: submitForm, fn: () => true, target: $formSubmitted, }); ``` ```ts ins={12-30} import { createEvent, createStore, createEffect } from "effector"; import { createAction } from "effector-action"; const submitForm = createEvent(); const $formData = createStore({ username: "", age: 0 }); const $formSubmitted = createStore(false); const sendFormFx = createEffect((formData: { username: string; age: number }) => { // some logic to send data to the server }); createAction(submitForm, { source: $formData, target: { sendForm: sendFormFx, formSubmitted: $formSubmitted, }, fn: (target, form) => { if (form.age >= 18 && form.username.length > 0) { target.sendForm(form); } target.formSubmitted(true); }, }); ``` ### Usage capabilities As mentioned, both operators are conceptually similar to each other, so you don't need to choose one over the other - you can use both in your application. However, there are some cases when [`createAction`](https://github.com/AlexeyDuybo/effector-action?tab=readme-ov-file#createaction) will be preferable over sample: 1. Conditional execution logic. When using sample, you may encounter difficulty in narrowing types after , which is not the case when using [`createAction`](https://github.com/AlexeyDuybo/effector-action?tab=readme-ov-file#createaction) due to the use of native language constructs that TypeScript understands well - `if`. 2. Grouping by trigger. It's also more convenient to use [`createAction`](https://github.com/AlexeyDuybo/effector-action?tab=readme-ov-file#createaction) when we have one common trigger, but different calculations are required for each `target`. Let's now look at the main operator usage capabilities: * You can update a store by passing it to `target`, the old value will be fully replaced with the new one: ```ts import { createEvent, createStore, sample } from "effector"; const $query = createStore(""); const queryChanged = createEvent(); sample({ clock: queryChanged, target: $query, }); ``` ```ts import { createStore, createEvent } from "effector"; import { createAction } from "effector-action"; const $query = createStore(""); const queryChanged = createEvent(); createAction(queryChanged, { target: $query, fn: (target, query) => { target(query); }, }); ``` * You can also trigger an effect or event by passing it to target; the arguments will be forwarded declaratively to the `target`: ```ts import { createEvent, sample } from "effector"; const updateQuery = createEvent(); const queryChanged = createEvent(); sample({ clock: updateQuery, target: queryChanged, }); updateQuery("new query"); ``` ```ts import { createEvent } from "effector"; import { createAction } from "effector-action"; const updateQuery = createEvent(); const queryChanged = createEvent(); createAction(updateQuery, { target: queryChanged, fn: (target, query) => { target(query); }, }); updateQuery("new query"); ``` * You can control the `target` call by condition, read more about this on the API page for sample: ```ts import { createEvent, createStore, sample } from "effector"; const $query = createStore(""); const $shouldUpdate = createStore(false); const queryChanged = createEvent(); sample({ clock: queryChanged, filter: $shouldUpdate, target: $query, }); ``` ```ts import { createStore, createEvent } from "effector"; import { createAction } from "effector-action"; const $query = createStore(""); const $shouldUpdate = createStore(false); const queryChanged = createEvent(); createAction(queryChanged, { source: { $shouldUpdate, }, target: $query, fn: (target, { shouldUpdate }, query) => { if (shouldUpdate) { target(query); } }, }); ``` * You can also perform calculations in the `fn` function, but keep in mind that it must be a pure function and synchronous. #### Limitations of `createAction` The `createAction` operator has an important restriction: when calling the same `target` multiple times, only the last one will be invoked: ```ts import { createStore, createEvent } from "effector"; import { createAction } from "effector-action"; const $counter = createStore(0); const increase = createEvent(); createAction(increase, { target: $counter, fn: (target, delta) => { target(delta); // only the last target call will be executed target(delta + 5); }, }); ``` ### How to use these operators Using these operators involves building atomic connections instead of one large block of code. For example, let's consider another application scenario - a search form with parameters. Let's first look at how we would write this in vanilla JavaScript: Suppose we have some state in a UI framework: ```ts const state = { query: "", category: "all", results: [], isLoading: false, error: null, }; ``` Functions to change state: ```ts function handleQueryChanged(payload) { // here can be any state change from React/Vue/Solid and other frameworks state.query = payload; } function handleCategoryChanged(payload) { // here can be any state change from React/Vue/Solid and other frameworks state.category = payload; } ``` And the main function for requesting data: ```ts async function handleSearchClick() { state.error = null; state.results = []; state.isLoading = true; try { const currentQuery = state.query; const currentCategory = state.category; // some API call const data = await apiCall(currentQuery, currentCategory); state.results = data; } catch (e) { state.error = e.message; } finally { state.isLoading = false; } } ``` All that's left is to call these functions in the UI at the right moment. With the `sample` or `createAction` operators, things work a bit differently - we will create atomic independent connections between units. First, let's rewrite the previous code using units: ```ts del={1-7} ins={9-14} // model.ts const state = { query: "", category: "all", results: [], isLoading: false, error: null, }; const $query = createStore(""); const $category = createStore("all"); const $results = createStore([]); const $error = createStore(null); const $isLoading = createStore(false); ``` We need events to change stores and also add logic to change these stores: ```ts del={1-7} ins={9-22} // model.ts function handleQueryChanged(payload) { state.query = payload; } function handleCategoryChanged(payload) { state.category = payload; } const queryChanged = createEvent(); const categoryChanged = createEvent(); sample({ clock: queryChanged, target: $query, }); sample({ clock: categoryChanged, target: $category, }); ``` And now we need to implement the main search logic: ```ts del={1-19} ins={21-51} // model.ts async function handleSearchClick() { state.error = null; state.results = []; state.isLoading = true; try { const currentQuery = state.query; const currentCategory = state.category; // some API call const data = await apiCall(currentQuery, currentCategory); state.results = data; } catch (e) { state.error = e.message; } finally { state.isLoading = false; } } const searchClicked = createEvent(); const searchFx = createEffect(async ({ query, category }) => { const data = await apiCall(query, category); return data; }); sample({ clock: searchClicked, source: { query: $query, category: $category, }, target: searchFx, }); sample({ clock: searchFx.$pending, target: $isLoading, }); sample({ clock: searchFx.failData, fn: (error) => error.message, target: $error, }); sample({ clock: searchFx.doneData, target: $results, }); ``` In the final form, we will have the following data model: ```ts // model.ts import { createStore, createEvent, createEffect, sample } from "effector"; const $query = createStore(""); const $category = createStore("all"); const $results = createStore([]); const $error = createStore(null); const $isLoading = createStore(false); const queryChanged = createEvent(); const categoryChanged = createEvent(); const searchClicked = createEvent(); const searchFx = createEffect(async ({ query, category }) => { const data = await apiCall(query, category); return data; }); sample({ clock: queryChanged, target: $query, }); sample({ clock: categoryChanged, target: $category, }); sample({ clock: searchClicked, source: { query: $query, category: $category, }, target: searchFx, }); sample({ clock: searchFx.$pending, target: $isLoading, }); sample({ clock: searchFx.failData, fn: (error) => error.message, target: $error, }); sample({ clock: searchFx.doneData, target: $results, }); ``` ### Related API and articles * **API** * sample - Operator for building connections between units * Event - Description of event and its methods * Store - Description of store and its methods * Effect - Description of effect and its methods * **Articles** * Guide on typing units and operators * Description of common errors, gotchas and way's for solving them * How to think in the effector paradigm # Side effects and async operations ## Side effects and async operations To work with side effects and any asynchronous logic, effector provides effects. A side effect is anything that can impact the purity of your function, for example: * HTTP requests to a server * Changing or touching global variables * Browser APIs such as `addEventListener`, `setTimeout`, and so on * Working with `localStorage`, `IndexedDB`, or other storage APIs * Any code that may throw or take noticeable time to complete ```ts import { createEffect, sample, createStore } from "effector"; const $user = createStore(null); const fetchUserFx = createEffect(async (userId: string) => { const response = await fetch(`/api/users/${userId}`); if (!response.ok) { throw new Error("Failed to fetch user"); } return response.json(); }); // when the effect succeeds we update the store with returned value sample({ clock: fetchUserFx.doneData, target: $user, }); ``` But why do we need effects at all? In effector most helpers such as store.on, sample.fn, and so on are pure — they work only with the arguments they receive. These functions **must not** be asynchronous or contain side effects because that would break predictability and reactivity. ### Advantages of effects An effect is a container for side effects or asynchronous functions that may throw or take an unknown amount of time. To connect effects to the reactive system they expose convenient properties. A few important ones: * pending — a store that shows whether the effect is running. Perfect for displaying a loader in UI. * doneData — an event fired when the effect completes successfully. * failData — another event triggered when the effect throws; it carries the thrown error. > WARNING Derived units: > > Every effect property is triggered by effector’s core. You must not try to call them manually. Because effects have their own events, working with them is similar to regular events. Let’s look at a simple example: ```ts // model.ts import { createEffect, createEvent, createStore, sample } from "effector"; export const $error = createStore(null); export const submit = createEvent(); // simple form submission wrapped into an effect const sendFormFx = createEffect(async ({ name, email }: { name: string; email: string }) => { try { await fetch("/api/user-data", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, email }), }); } catch { throw new Error("Failed to send form"); } }); export const $isLoading = sendFormFx.pending; sample({ clock: sendFormFx.failData, fn: (error) => error.message, target: $error, }); sample({ clock: submit, target: sendFormFx, }); ``` > INFO One argument: > > Effects, just like events, accept only one argument. Use an object (e.g. `{ name, email }`) if you need to pass multiple values. In UI we trigger the `submit` event, show a loader while the request is running, and display the error if there is one: ```tsx "{isLoading &&
    Loading...
    }" "{error &&
    {error}
    }" "onSubmit" // view.tsx import { useUnit } from "effector-react"; import { $error, $isLoading, submit } from "./model.ts"; const Form = () => { const { isLoading, error } = useUnit({ isLoading: $isLoading, error: $error, }); const onSubmit = useUnit(submit); return (
    {isLoading &&
    Loading...
    } {error &&
    {error}
    }
    ); }; ``` Let’s highlight the reactive connections in the model. First we connect the failData event to the `$error` store so that the error message is saved whenever the effect fails: ```ts sample({ clock: sendFormFx.failData, fn: (error) => error.message, target: $error, }); ``` When the `submit` event fires we just put the effect into target: ```ts sample({ clock: submit, target: sendFormFx, }); ``` ### Calling effects Effect calls are identical to event calls. You can grab an effect inside a component with `useUnit` and call it as a function: ```ts const fetchUser = useUnit(fetchUserFx); ``` However, it is better not to expose our UI layer to too much business logic. A common alternative is to export an event from the model, use it in the view, and bind that event to the effect with sample. The arguments passed to the event will be forwarded to the effect: ```ts import { createEvent, sample } from "effector"; export const updateProfileButtonPressed = createEvent(); sample({ clock: updateProfileButtonPressed, target: fetchUserFx, }); ``` > TIP Why do I need an event for an effect call?: > > On the How to think in the effector paradigm page we explain why UI and business logic should stay separate. You can also call effects inside other effects: ```ts import { createEffect } from "effector"; const fetchInitialData = createEffect(async (userId: string) => { const userData = await getUserByIdFx(userId); const friends = await getUserByIds(userData.friends); return userData.name; }); ``` #### Calling events inside an effect It is also possible to trigger events inside an effect — handy when you need to emit something on a timer: ```ts import { createEffect, createEvent } from "effector"; const tick = createEvent(); const fetchInitialData = createEffect(async () => { // remember to clean up the id later! const id = setInterval(() => { tick(); }, 1000); }); ``` If you work with scopes, this pattern may lead to scope loss. In that case wrap the handler with scopeBind. No extra work is required outside of scoped environments. ### Update a store when the effect completes A classic scenario is updating a store once the effect finishes. The logic is the same as with events: subscribe to doneData and point the target to the desired store: ```ts import { createStore, createEffect } from "effector"; const fetchUserNameFx = createEffect(async (userId: string) => { const userData = await fetch(`/api/users/${userId}`); return userData.name; }); const $error = createStore(null); const $userName = createStore(""); const $isLoading = fetchUserNameFx.pending; sample({ clock: fetchUserNameFx.doneData, target: $userName, }); sample({ clock: fetchUserNameFx.failData, fn: (error) => error.message, target: $error, }); ``` ### Error handling An effect knows when an error occurs during execution and forwards it to the failData event. Sometimes we want to throw our own error instead of a plain `Error` instance: ```ts import { createEffect } from "effector"; class CustomError extends Error { // implementation } const effect = createEffect(async () => { const response = await fetch(`/api/users/${userId}`); if (!response.ok) { // You can throw errors that will be caught by the .failData handler throw new CustomError(`Failed to fetch user: ${response.statusText}`); } return response.json(); }); ``` The code above runs just fine, but if we subscribe to failData we will still see the type as `Error` rather than our `CustomError`: ```ts sample({ clock: effect.failData, // error is typed as Error, not CustomError fn: (error) => error.message, target: $error, }); ``` This happens because of typing: by default an effect assumes it will throw `Error` and has no way of knowing that we plan to throw `CustomError`. The solution is to pass the full type signature to the createEffect generic, including params, done type, and the error type: ```ts ins="" import { createEffect } from "effector"; class CustomError extends Error { // implementation } const effect = createEffect(async () => { const response = await fetch(`/api/users/${userId}`); if (!response.ok) { throw new CustomError(`Failed to fetch user: ${response.statusText}`); } return response.json(); }); ``` Read more about typing effects and other units on the TypeScript page. ### Reusing effects A common case is having a shared effect such as `fetchShopCardsFx` that you reuse on multiple screens while still wanting to subscribe to events like doneData or failData. If you subscribe directly to the shared effect, its listeners fire everywhere because effector units are declared statically at module level. The solution is attach, which creates a copy of the effect. You can then listen to that copy locally: ```ts "attach" import { createEffect, attach, createEvent } from "effector"; const showNotification = createEvent(); // somewhere in shared code const fetchShopCardsFx = createEffect(async () => { const response = await fetch("/api/shop-cards"); return response.json(); }); // local copy that we can safely subscribe to const fetchShopCardsAttachedFx = attach({ effect: fetchShopCardsFx, }); sample({ clock: fetchShopCardsAttachedFx.failData, target: showNotification, }); ``` When an attached effect created via attach runs, the original effect provided in effect runs as well. ### Related API and docs * **API** * Effect — description of effects and their methods * createEffect — helper for creating effects * attach — create new effects based on existing ones * **Articles** * Typing effects and other units * Why units must be declared statically at module level * Connecting units with * Isolated scopes, when to use them, and the scope-loss problem # Computation priority ## Computation priority For sure, you've noticed that function should be pure... or watch if there is a place for side effect. We will talk about this in the current section – **Computation priority** A real example of queue priority — people waiting for medical treatment in a hospital, extreme emergency cases will have the highest priority and move to the start of the queue and less significant to the end. Computation priority allows us to have side effects, and it's one of the main reasons to create this concept: * Letting pure functions to execute first. * Side effects can follow a consistent state of the application. Actually, pure computation cannot be observed out of the scope, therefore, the definition of ***pure computation*** used in this library gives us an opportunity to optimize grouping. Priority: [Source code](https://github.com/effector/effector/blob/master/src/effector/kernel.ts#L169) ``` 1. child -> forward 2. pure -> map, on 3. sampler -> sample, guard, combine 4. effect -> watch, effect handler ``` > Whenever you allow side effects in pure computations, the library will work by the worst scenario. Thereby, increasing non-consistency of application and breaking pure computations. Don't ignore that. Let's consider prioritizing in the example below. ```js let count = 0; const fx = createEffect(() => { // side effect 1 count += 1; }); fx.done.watch(() => { // side effect 1 already executed console.log("expect count to be 1", count === 1); // side effect 2 count += 1; }); fx(); // side effect 1 already executed // side effect 2 already executed as well // that's what we expected to happen // that's watchmen effect console.log("expect count to be 2", count === 2); // example which violated that agreement: setState in react // which defer any side effect long after setState call itself ``` Try it > INFO: > > Whenever a library notices side effect in a pure function it moves it to the end of the [**priority queue**](https://en.wikipedia.org/wiki/Priority_queue). We hope that this information cleared some things on how the library works. # Glossary ## Glossary Glossary of basic terms in effector. ### Event *Event* is a function you can subscribe to. It can be an intention to change the store, indication of something happening in the application, a command to be executed, aggregated analytics trigger and so on. Event in api documentation ### Store *Store* is an object that holds state. There can be multiple stores. Store in api documentation ### Derived store *Derived store* is a read-only store that is created from other stores and automatically updates when source stores change. Derived stores are created using methods like Store.map, combine, or as properties of effects (e.g., Effect.pending). Key characteristics: * Cannot be modified directly by events * Cannot be used as `target` in sample * Always depends on source stores * Updates automatically when source stores change Store in api documentation ### Effect *Effect* is a container for (possibly async) side effects. It exposes special events and stores, such as `.pending`, `.done`, `.fail`, `.finally`, etc... It can be safely used in place of the original async function. It returns promise with the result of a function call. The only requirement for the function: * **Must** have zero or one argument Effect in api documentation ### Domain *Domain* is a namespace for your events, stores and effects. Domains are notified when events, stores, effects, or nested domains are created via `.onCreateEvent`, `.onCreateStore`, `.onCreateEffect`, `.onCreateDomain` methods. It is useful for logging or other side effects. Domain in api documentation ### Unit Data type used to describe business logic of applications. Most of the effector methods deal with unit processing. There are five unit types: Store, Event, Effect, Domain and Scope. ### Common unit Common units can be used to trigger updates of other units. There are three common unit types: Store, Event and Effect. **When a method accepts units, it means that it accepts events, effects, and stores** as a source of reactive updates. ### Purity Most of the functions in api must not call other events or effects: it's easier to reason about application's data flow when imperative triggers are grouped inside watchers and effect handlers rather than spread across entire business logic. **Correct**, imperative: ```js import { createStore, createEvent } from "effector"; const submitLoginSize = createEvent(); const $login = createStore("guest"); const $loginSize = $login.map((login) => login.length); $loginSize.watch((size) => { submitLoginSize(size); }); ``` Try it Reference: Store.map, Store.watch **Better**, declarative: ```js import { createStore, createEvent, sample } from "effector"; const submitLoginSize = createEvent(); const $login = createStore("guest"); const $loginSize = $login.map((login) => login.length); sample({ clock: $loginSize, target: submitLoginSize, }); ``` Try it Reference: sample **Incorrect**: ```js import { createStore, createEvent } from "effector"; const submitLoginSize = createEvent(); const $login = createStore("guest"); const $loginSize = $login.map((login) => { // no! use `sample` instead submitLoginSize(login.length); return login.length; }); ``` ### Reducer ```typescript type StoreReducer = (state: State, payload: E) => State | void; type EventOrEffectReducer = (state: T, payload: E) => T; ``` *Reducer* calculates a new state given the previous state and an event's payload. For stores, if reducer returns undefined or the same state (`===`), then there will be no update for a given store. ### Watcher ```typescript type Watcher = (update: T) => any; ``` *Watcher* is used for **side effects**. Accepted by Event.watch, Store.watch and Domain.onCreate\* hooks. Return value of a watcher is ignored. ### Subscription ```ts import { type Subscription } from "effector"; ``` Looks like: ```typescript type Subscription = { (): void; unsubscribe(): void; }; ``` **Function**, returned by forward, Event.watch, Store.watch and some other methods. Used for cancelling a subscription. After the first call, subscription will do nothing. > WARNING: > > **Managing subscriptions manually distracts from business logic improvements.**

    > Effector provides a wide range of features to minimize the need to remove subscriptions. This sets it apart from most other reactive libraries. [effect]: /en/api/effector/Effect [store]: /en/api/effector/Store [event]: /en/api/effector/Event [domain]: /en/api/effector/Domain [scope]: /en/api/effector/Scope # Prior Art ## Prior Art ### Papers * **Functional Pearl. Weaving a Web** [\[pdf\]](https://zero-bias-papers.s3-eu-west-1.amazonaws.com/weaver+zipper.pdf) *Ralf Hinze and Johan Jeuring* * **A graph model of data and workflow provenance** [\[pdf\]](https://zero-bias-papers.s3-eu-west-1.amazonaws.com/A+graph+model+of+data+and+workflow+provenance.pdf)
    *Umut Acar, Peter Buneman, James Cheney, Jan Van den Bussche, Natalia Kwasnikowska and Stijn Vansummeren* * **An Applicative Control-Flow Graph Based on Huet’s Zipper** [\[pdf\]](http://zero-bias-papers.s3-website-eu-west-1.amazonaws.com/zipcfg.pdf)
    *Norman Ramsey and Joao Dias* * **Elm: Concurrent FRP for Functional GUIs** [\[pdf\]](https://zero-bias-papers.s3-eu-west-1.amazonaws.com/elm-concurrent-frp.pdf)
    *Evan Czaplicki* * **Inductive Graphs and Functional Graph Algorithms** [\[pdf\]](https://zero-bias-papers.s3-eu-west-1.amazonaws.com/Inductive+Graphs+and+Functional+Graph+Algorithms.pdf)
    *Martin Erwig* * **Notes on Graph Algorithms Used in Optimizing Compilers** [\[pdf\]](https://zero-bias-papers.s3-eu-west-1.amazonaws.com/Graph+Algorithms+Used+in+Optimizing+Compilers.pdf)
    *Carl D. Offner* * **Backtracking, Interleaving, and Terminating Monad Transformers** [\[pdf\]](https://zero-bias-papers.s3-eu-west-1.amazonaws.com/Backtracking%2C+Interleaving%2C+and+Terminating+Monad+Transformers.pdf)
    *Oleg Kiselyov, Chung-chieh Shan, Daniel P. Friedman and Amr Sabry* * **Typed Tagless Final Interpreters** [\[pdf\]](https://zero-bias-papers.s3-eu-west-1.amazonaws.com/Typed+Tagless+Final+Interpreters.pdf) *Oleg Kiselyov* ### Books * **Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions** [\[book\]](https://www.amazon.com/o/asin/0321200683/ref=nosim/enterpriseint-20), [\[messaging patterns overview\]](https://www.enterpriseintegrationpatterns.com/patterns/messaging/)
    *Gregor Hohpe and Bobby Woolf* ### API * [re-frame](https://github.com/day8/re-frame) * [flux](https://facebook.github.io/flux/) * [redux](https://redux.js.org/) * [redux-act](https://github.com/pauldijou/redux-act) * [most](https://github.com/cujojs/most) * nodejs [events](https://nodejs.org/dist/latest-v12.x/docs/api/events.html#events_emitter_on_eventname_listener) # SIDs ## SIDs Effector is based on idea of atomic store. It means that any application does not have some centralized state controller or other entry point to collect all states in one place. So, there is the question — how to distinguish units between different environments? For example, if we ran an application on the server and serialize its state to JSON, how do we know which part of the JSON should be filled in a particular store on the client? Let's discuss how this problem solved by other state managers. ### Other state managers #### Single store In the state manager with single store (e.g. Redux), this problem does not exist at all. It is a single store, which can be serialized and deserialized without any additional information. > INFO: > > Actually, single store forces you to create unique names of each part of it implicitly. In any object you won't be able to create duplicate keys, so the path to store slice is a unique identifier of this slice. ```ts // server.ts import { createStore } from "single-store-state-manager"; function handlerRequest() { const store = createStore({ initialValue: null }); return { // It is possible to just serialize the whole store state: JSON.stringify(store.getState()), }; } // client.ts import { createStore } from "single-store-state-manager"; // Let's assume that server put the state into the HTML const serverState = readServerStateFromWindow(); const store = createStore({ // Just parse the whole state and use it as client state initialValue: JSON.parse(serverState), }); ``` It's great that you do not need any additional tools for serialization and deserialization, but single store has a few problems: * It does not support tree-shaking and code-splitting, you have to load the whole store anyway * Because its architecture, it requires some additional tools for fixing performance (like `reselect`) * It does not support any kind of micro-frontends and stuff which is getting bigger recently #### Multi stores Unfortunately, state managers that built around idea of multi stores do not solve this problem good. Some tools offer single store like solutions (MobX), some does not try to solve this issue at all (Recoil, Zustand). > INFO: > > E.g., the common pattern to solve serialization problem in MobX is [Root Store Pattern](https://dev.to/ivandotv/mobx-root-store-pattern-with-react-hooks-318d) which is destroying the whole idea of multi stores. So, we are considering SSR as a first class citizen of modern web applications, and we are going to support code-splitting or micro-frontends. ### Unique identifiers for every store Because of multi-store architecture, Effector requires a unique identifier for every store. It is a string that is used to distinguish stores between different environments. In Effector's world this kind of strings are called `sid`. \:::tip TL;DR `sid` is a unique identifier of a store. It is used to distinguish stores between different environments. \::: Let's add it to some stores: ```ts const $name = createStore(null, { sid: "name" }); const $age = createStore(null, { sid: "age" }); ``` Now, we can serialize and deserialize stores: ```ts // server.ts async function handlerRequest() { // create isolated instance of application const scope = fork(); // fill some data to stores await allSettled($name, { scope, params: "Igor" }); await allSettled($age, { scope, params: 25 }); const state = JSON.serialize(serialize(scope)); // -> { "name": "Igor", "age": 25 } return { state }; } ``` After this code, we have a serialized state of our application. It is a plain object with stores' values. We can put it back to the stores on the client: ```ts // Let's assume that server put the state into the HTML const serverState = readServerStateFromWindow(); const scope = fork({ // Just parse the whole state and use it as client state values: JSON.parse(serverState), }); ``` Of course, it's a lot of boring jobs to write `sid` for every store. Effector provides a way to do it automatically with code transformation plugins. #### Automatic way For sure, manually creating unique ids is a quite boring job. Thankfully, there are effector/babel-plugin and @effector/swc-plugin, which will provide SIDs automatically. Because code-transpilation tools are working at the file level and are run before bundling happens – it is possible to make SIDs **stable** for every environment. > TIP: > > It is preferable to use effector/babel-plugin or @effector/swc-plugin instead of adding SIDs manually. **Code example** Notice, that there is no central point at all – any event of any "feature" can be triggered from anywhere and the rest of them will react accordingly. ```tsx // src/features/first-name/model.ts import { createStore, createEvent } from "effector"; export const firstNameChanged = createEvent(); export const $firstName = createStore(""); $firstName.on(firstNameChanged, (_, firstName) => firstName); // src/features/last-name/model.ts import { createStore, createEvent } from "effector"; export const lastNameChanged = createEvent(); export const $lastName = createStore(""); $lastName.on(lastNameChanged, (_, lastName) => lastName); // src/features/form/model.ts import { createEvent, sample, combine } from "effector"; import { $firstName, firstNameChanged } from "@/features/first-name"; import { $lastName, lastNameChanged } from "@/features/last-name"; export const formValuesFilled = createEvent<{ firstName: string; lastName: string }>(); export const $fullName = combine($firstName, $lastName, (first, last) => `${first} ${last}`); sample({ clock: formValuesFilled, fn: (values) => values.firstName, target: firstNameChanged, }); sample({ clock: formValuesFilled, fn: (values) => values.lastName, target: lastNameChanged, }); ``` If this application was a SPA or any other kind of client-only app — this would be the end of the article. #### Serialization boundary But in the case of Server Side Rendering, there is always a **serialization boundary** — a point, where all state is stringified, added to a server response, and sent to a client browser. ##### Problem And at this point we **still need to collect the states of all stores of the app** somehow! Also, after the client browser has received a page — we need to "hydrate" everything back: unpack these values at the client and add this "server-calculated" state to client-side instances of all stores. ##### Solution This is a hard problem and to solve this, `effector` needs a way to connect the "server-calculated" state of some store with its client-side instance. While **it could be** done by introducing a "root store" or something like that, which would manage store instances and their state for us, it would also bring to us all the downsides of this approach, e.g. much more complicated code-splitting – so this is still undesirable. This is where SIDs will help us a lot. Because SID is, by definition, the same for the same store in any environment, `effector` can simply rely on it to handle state serializing and hydration. ##### Example This is a generic server-side rendering handler. The `renderHtmlToString` function is an implementation detail, which will depend on the framework you use. ```tsx // src/server/handler.ts import { fork, allSettled, serialize } from "effector"; import { formValuesFilled } from "@/features/form"; async function handleServerRequest(req) { const scope = fork(); // creates isolated container for application state // calculates the state of the app in this scope await allSettled(formValuesFilled, { scope, params: { firstName: "John", lastName: "Doe", }, }); // extract scope values to simple js object of `{[storeSid]: storeState}` const values = serialize(scope); const serializedState = JSON.stringify(values); return renderHtmlToString({ scripts: [ ` `, ], }); } ``` Notice, that there are no direct imports of any stores of the application here. The state is collected automatically and its serialized version already has all the information, which will be needed for hydration. When the generated response arrives in a client browser, the server state must be hydrated to the client stores. Thanks to SIDs, state hydration also works automatically: ```tsx // src/client/index.ts import { Provider } from "effector-react"; const serverState = window._SERVER_STATE_; const clientScope = fork({ values: serverState, // simply assign server state to scope }); clientScope.getState($lastName); // "Doe" hydrateApp( , ); ``` At this point, the state of all stores in the `clientScope` is the same, as it was at the server and there was **zero** manual work to do it. ### Unique SIDs The stability of SIDs is ensured by the fact, that they are added to the code before any bundling has happened. But since both `babel` and `swc` plugins are able "to see" contents of one file at each moment, there is a case, where SIDs will be stable, but **might not be unique** To understand why, we need to dive a bit deeper into plugin internals. Both `effector` plugins use the same approach to code transformation. Basically, they do two things: 1. Add `sid`-s and any other meta-information to raw Effector's factories calls, like `createStore` or `createEvent`. 2. Wrap any custom factories with `withFactory` helper that allows you to make `sid`-s of inner units unique as well. #### Built-in unit factories Let's take a look at the first case. For the following source code: ```ts const $name = createStore(null); ``` The plugin will apply these transformations: ```ts const $name = createStore(null, { sid: "j3l44" }); ``` > TIP: > > Plugins create `sid`-s as a hash of the location in the source code of a unit. It allows making `sid`-s unique and stable. #### Custom factories The second case is about custom factories. These are usually created to abstract away some common pattern. Examples of custom factories: * `createQuery`, `createMutation` from [`farfetched`](https://ff.effector.dev/) * `debounce`, `throttle`, etc from [`patronum`](https://patronum.effector.dev/) * Any custom factory in your code, e.g. factory of a [feature-flag entity](https://ff.effector.dev/recipes/feature_flags.html) > TIP: > > farfetched, patronum, @effector/reflect, atomic-router and @withease/factories are supported by default and doesn't need additional configuration For this explanation, we will create a very simple factory: ```ts // src/shared/lib/create-name/index.ts export function createName() { const updateName = createEvent(); const $name = createStore(null); $name.on(updateName, (_, nextName) => nextName); return { $name }; } // src/feature/persons/model.ts import { createName } from "@/shared/lib/create-name"; const personOne = createName(); const personTwo = createName(); ``` First, the plugin will add `sid` to the inner stores of the factory ```ts // src/shared/lib/create-name/index.ts export function createName() { const updateName = createEvent(); const $name = createStore(null, { sid: "ffds2" }); $name.on(updateName, (_, nextName) => nextName); return { $name }; } // src/feature/persons/model.ts import { createName } from "@/shared/lib/create-name"; const personOne = createName(); const personTwo = createName(); ``` But it's not enough, because we can create two instances of `createName` and internal stores of both of these instances will have the same SIDs! These SIDs will be stable, but not unique. To fix it we need to inform the plugin about our custom factory: ```json // .babelrc { "plugins": [ [ "effector/babel-plugin", { "factories": ["@/shared/lib/create-name"] } ] ] } ``` Since the plugin "sees" only one file at a time, we need to provide it with the actual import path used in the module. > TIP: > > If relative import paths are used in the module, then the full path from the project root must be added to the `factories` list, so the plugin could resolve it. > > If absolute or aliased (like in the example) paths are used, then specifically this aliased path must be added to the `factories` list. > > Most of the popular ecosystem projects are already included in plugin's default settings. Now the plugin knows about our factory and it will wrap `createName` with the internal `withFactory` helper: ```ts // src/shared/lib/create-name/index.ts export function createName() { const updateName = createEvent(); const $name = createStore(null, { sid: "ffds2" }); $name.on(updateName, (_, nextName) => nextName); return { $name }; } // src/feature/persons/model.ts import { withFactory } from "effector"; import { createName } from "@/shared/lib/create-name"; const personOne = withFactory({ sid: "gre24f", fn: () => createName(), }); const personTwo = withFactory({ sid: "lpefgd", fn: () => createName(), }); ``` Thanks to that `sid`-s of inner units of a factory are also unique, and we can safely serialize and deserialize them. ```ts personOne.$name.sid; // gre24f|ffds2 personTwo.$name.sid; // lpefgd|ffds2 ``` #### How `withFactory` works `withFactory` is a helper that allows to create unique `sid`-s for inner units. It is a function that accepts an object with `sid` and `fn` properties. `sid` is a unique identifier of the factory, and `fn` is a function that creates units. Internal implementation of `withFactory` is pretty simple, it puts received `sid` to the global scope before `fn` call, and removes it after. Any Effector's creator function tries to read this global value while creating and append its value to the `sid` of the unit. ```ts let globalSid = null; function withFactory({ sid, fn }) { globalSid = sid; const result = fn(); globalSid = null; return result; } function createStore(initialValue, { sid }) { if (globalSid) { sid = `${globalSid}|${sid}`; } // ... } ``` Because of single thread nature of JavaScript, it is safe to use global variables for this purpose. > INFO: > > Of course, the real implementation is a bit more complicated, but the idea is the same. ### Summary 1. Any multi-store state manager requires unique identifiers for every store to distinguish them between different environments. 2. In Effector's world this kind of strings are called `sid`. 3. Plugins for code transformations add `sid`-s and meta-information to raw Effector's units creation, like `createStore` or `createEvent`. 4. Plugins for code transformations wrap custom factories with `withFactory` helper that allow to make `sid`-s of inner units unique as well. # Best Practices and Recommendations import Tabs from '@components/Tabs/Tabs.astro'; import TabItem from '@components/Tabs/TabItem.astro'; ## Best Practices in Effector This section contains recommendations for effective work with Effector, based on community experience and the development team. ### Keep Stores Small Unlike Redux, in Effector it's recommended to make stores as atomic as possible. Let's explore why this is important and what advantages it provides. Large stores with multiple fields create several problems: * Unnecessary re-renders: When any field changes, all components subscribed to the store update * Heavy computations: Each update requires copying the entire object * Unnecessary calculations: if you have derived stores depending on a large store, they will be recalculated Atomic stores allow: * Updating only what actually changed * Subscribing only to needed data * More efficient work with reactive dependencies ```ts // ❌ Big store - any change triggers update of everything const $bigStore = createStore({ profile: {/* many fields */}, settings: {/* many fields */}, posts: [ /* many posts */ ] }) // ✅ Atomic stores - precise updates const $userName = createStore('') const $userEmail = createStore('') const $posts = createStore([]) const $settings = createStore({}) // Component subscribes only to needed data const UserName = () => { const name = useUnit($userName) // Updates only when name changes return

    {name}

    } ``` Rules for atomic stores: * One store = one responsibility * Store should be indivisible * Stores can be combined using combine * Store update should not affect other data ### Immer for Complex Objects If your store contains nested structures, you can use the beloved Immer for simplified updates: ```ts import { createStore } from 'effector'; import { produce } from 'immer'; const $users = createStore([]); $users.on(userUpdated, (users, updatedUser) => produce(users, (draft) => { const user = draft.find((u) => u.id === updatedUser.id); if (user) { user.profile.settings.theme = updatedUser.profile.settings.theme; } }), ); ``` ### Explicit app start To gain better control over your application’s lifecycle, we recommend defining explicit events such as `appStarted`. If you need more granular control, don’t hesitate to create additional events. You can find more details on the Explicit app start page. ```ts export const appStarted = createEvent(); ``` ### Use `scope` The effector team recommends always using Scope, even if your application doesn't use SSR. This is necessary so that in the future you can easily migrate to working with `Scope`. ### `useUnit` Hook Using the useUnit hook is the recommended way to work with units when using frameworks (📘React, 📗Vue, and 📘Solid). Why you should use `useUnit`: * Correct work with stores * Optimized updates * Automatic work with `Scope` – units know which scope they were called in ### Pure Functions Use pure functions everywhere except effects for data processing, this ensures: * Deterministic result * No side effects * Easier to test * Easier to maintain > TIP This is work for effects: > > If your code can throw an error or can end in success/failure - that's an excellent place for effects. ### Debugging We strongly recommend using the patronum library and the debug method. ```ts import { createStore, createEvent, createEffect } from 'effector'; import { debug } from 'patronum/debug'; const event = createEvent(); const effect = createEffect().use((payload) => Promise.resolve('result' + payload)); const $store = createStore(0) .on(event, (state, value) => state + value) .on(effect.done, (state) => state * 10); debug($store, event, effect); event(5); effect('demo'); // => [store] $store 1 // => [event] event 5 // => [store] $store 6 // => [effect] effect demo // => [effect] effect.done {"params":"demo", "result": "resultdemo"} // => [store] $store 60 ``` However, nothing prevents you from using `.watch` or createWatch for debugging. ### Factories Factory creation is a common pattern when working with effector, it makes it easier to use similar code. However, you may encounter a problem with identical sids that can interfere with SSR. To avoid this problem, we recommend using the [@withease/factories](https://withease.effector.dev/factories/) library. If your environment does not allow adding additional dependencies, you can create your own factory following these guidelines. ### Working with Network For convenient effector work with network requests, you can use farfetched. Farfetched provides: * Mutations and queries * Ready API for caching and more * Framework independence ### Effector Utils The Effector ecosystem includes the [patronum](https://patronum.effector.dev/operators/) library, which provides ready solutions for working with units: * State management (`condition`, `status`, etc.) * Working with time (`debounce`, `interval`, etc.) * Predicate functions (`not`, `or`, `once`, etc.) ### Simplifying Complex Logic with `createAction` [`effector-action`](https://github.com/AlexeyDuybo/effector-action) is a library that allows you to write imperative code for complex conditional logic while maintaining effector's declarative nature. Moreover, `effector-action` helps make your code more readable: ```ts import { sample } from 'effector'; sample({ clock: formSubmitted, source: { form: $form, settings: $settings, user: $user, }, filter: ({ form }) => form.isValid, fn: ({ form, settings, user }) => ({ data: form, theme: settings.theme, }), target: submitFormFx, }); sample({ clock: formSubmitted, source: $form, filter: (form) => !form.isValid, target: showErrorMessageFx, }); sample({ clock: submitFormFx.done, source: $settings, filter: (settings) => settings.sendNotifications, target: sendNotificationFx, }); ``` ```ts import { createAction } from 'effector-action'; const submitForm = createAction({ source: { form: $form, settings: $settings, user: $user, }, target: { submitFormFx, showErrorMessageFx, sendNotificationFx, }, fn: (target, { form, settings, user }) => { if (!form.isValid) { target.showErrorMessageFx(form.errors); return; } target.submitFormFx({ data: form, theme: settings.theme, }); }, }); createAction(submitFormFx.done, { source: $settings, target: sendNotificationFx, fn: (sendNotification, settings) => { if (settings.sendNotifications) { sendNotification(); } }, }); submitForm(); ``` ### Naming Use accepted naming conventions: * For stores – prefix `$` * For effects – postfix `fx`, this will help you distinguish your effects from events * For events – no rules, however, we suggest naming events that directly trigger store updates as if they've already happened. ```ts const updateUserNameFx = createEffect(() => {}); const userNameUpdated = createEvent(); const $userName = createStore('JS'); $userName.on(userNameUpdated, (_, newName) => newName); userNameUpdated('TS'); ``` > INFO Naming Convention: > > The choice between prefix or postfix is mainly a matter of personal preference. This is necessary to improve the search experience in your IDE. ### Anti-patterns #### Using watch for Logic watch should only be used for debugging. For logic, use sample, guard, or effects. ```ts // logic in watch $user.watch((user) => { localStorage.setItem('user', JSON.stringify(user)); api.trackUserUpdate(user); someEvent(user.id); }); ``` ```ts // separate effects for side effects const saveToStorageFx = createEffect((user: User) => localStorage.setItem('user', JSON.stringify(user)), ); const trackUpdateFx = createEffect((user: User) => api.trackUserUpdate(user)); // connect through sample sample({ clock: $user, target: [saveToStorageFx, trackUpdateFx], }); // for events also use sample sample({ clock: $user, fn: (user) => user.id, target: someEvent, }); ``` #### Complex Nested samples Avoid complex and nested chains of sample. #### Abstract Names in Callbacks Use meaningful names instead of abstract `value`, `data`, `item`. ```ts $users.on(userAdded, (state, payload) => [...state, payload]); sample({ clock: buttonClicked, source: $data, fn: (data) => data, target: someFx, }); ``` ```ts $users.on(userAdded, (users, newUser) => [...users, newUser]); sample({ clock: buttonClicked, source: $userData, fn: (userData) => userData, target: updateUserFx, }); ``` #### Imperative Calls in Effects Don't call events or effects imperatively inside other effects, instead use declarative style. ```ts const loginFx = createEffect(async (params) => { const user = await api.login(params); // imperative calls setUser(user); redirectFx('/dashboard'); showNotification('Welcome!'); return user; }); ``` ```ts const loginFx = createEffect((params) => api.login(params)); // Connect through sample sample({ clock: loginFx.doneData, target: [ $user, // update store redirectToDashboardFx, showWelcomeNotificationFx, ], }); ``` #### Using getState Don't use `$store.getState` to get values. If you need to get data from some store, pass it there, for example in `source` in `sample`: ```ts const submitFormFx = createEffect((formData) => { // get values through getState const user = $user.getState(); const settings = $settings.getState(); return api.submit({ ...formData, userId: user.id, theme: settings.theme, }); }); ``` ```ts // get values through parameters const submitFormFx = createEffect(({ form, userId, theme }) => {}); // get all necessary data through sample sample({ clock: formSubmitted, source: { form: $form, user: $user, settings: $settings, }, fn: ({ form, user, settings }) => ({ form, userId: user.id, theme: settings.theme, }), target: submitFormFx, }); ``` #### Business Logic in UI Don't put your logic in UI elements, this is the main philosophy of effector and what effector tries to free you from, namely the dependency of logic on UI. #### Creating units at runtime Never create units or logic via `sample` between them at runtime or in any other dynamic way. Correct unit initialization must be done statically at the module level. Brief summary of anti-patterns: 1. Don't use `watch` for logic, only for debugging 2. Avoid direct mutations in stores 3. Don't create complex nested `sample`, they're hard to read 4. Don't use large stores, use an atomic approach 5. Use meaningful parameter names, not abstract ones 6. Don't call events inside effects imperatively 7. Don't use `$store.getState` for work 8. Don't put logic in UI # Handle events in UI-frameworks ## Handle events in UI-frameworks Sometimes you need to do something on UI-framework layer when an event is fired on effector layer. For example, you may want to show a notification when a request for data is failed. In this article, we will look into a way to do it. ### The problem > TIP UI-framework: > > In this article, we will use [React](https://reactjs.org/) as an example of a UI-framework. However, the same principles can be applied to any other UI-framework. Let us imagine that we have an application uses [Ant Design](https://ant.design/) and its [notification system](https://ant.design/components/notification). It is pretty straightforward to show a notification on UI-layer ```tsx import { notification } from "antd"; function App() { const [api, contextHolder] = notification.useNotification(); const showNotification = () => { api.info({ message: "Hello, React", description: "Notification from UI-layer", }); }; return ( <> {contextHolder} ); } ``` But what if we want to show a notification when a request for data is failed? The whole data-flow of the application should not be exposed to the UI-layer. So, we need to find a way to handle events on UI-layer without exposing the whole data-flow. Let us say that we have an event responsible for data loading failure: ```ts // model.ts import { createEvent } from "effector"; const dataLoadingFailed = createEvent<{ reason: string }>(); ``` Our application calls it every time when a request for data is failed, and we need to listen to it on UI-layer. ### The solution We need to bound `dataLoadingFailed` and `notification.useNotification` somehow. Let us take a look on a ideal solution and a couple of not-so-good solutions. #### Save `notification` instance to a store The best way is saving `notification` API-instance to a store and using it thru effect. Let us create a couple new units to do it. ```ts // notifications.ts import { createEvent, createStore, sample } from "effector"; // We will use instance from this Store in the application const $notificationApi = createStore(null); // It has to be called every time when a new instance of notification API is created export const notificationApiChanged = createEvent(); // Save new instance to the Store sample({ clock: notificationApiChanged, target: $notificationApi, }); ``` Now we have to call `notificationApiChanged` to save `notification` API-instance to store `$notificationApi`. ```tsx {8-15} import { notification } from "antd"; import { useEffect } from "react"; import { useUnit } from "effector-react"; import { notificationApiChanged } from "./notifications"; function App() { // use useUnit to respect Fork API rules const onNewApiInstance = useUnit(notificationApiChanged); const [api, contextHolder] = notification.useNotification(); // call onNewApiInstance on every change of api useEffect(() => { onNewApiInstance(api); }, [api]); return ( <> {contextHolder} {/* ...the rest of the application */} ); } ``` After that, we have a valid store `$notificationApi` with `notification` API-instance. We can use it in any place of the application. Let us create a couple effects to work with it comfortably. ```ts // notifications.ts import { attach } from "effector"; // ... export const showWarningFx = attach({ source: $notificationApi, effect(api, { message, description }) { if (!api) { throw new Error("Notification API is not ready"); } api.warning({ message, description }); }, }); ``` > TIP about attach: > > attach is a function that allows to bind specific store to an effect. It means that we can use `notificationApi` in `showWarningFx` without passing it as a parameter. Effect `showWarningFx` can be used in any place of the application without any additional hustle. ```ts {8-13} // model.ts import { createEvent, sample } from "effector"; import { showWarningFx } from "./notifications"; const dataLoadingFailed = createEvent<{ reason: string }>(); // Show warning when dataLoadingFailed is happened sample({ clock: dataLoadingFailed, fn: ({ reason }) => ({ message: reason }), target: showWarningFx, }); ``` Now we have a valid solution to handle events on UI-layer without exposing the whole data-flow. This approach you can use for any UI API, even put a router in the framework and manage it from the data model. However, if you want to know why other (maybe more obvious) solutions are not so good, you can read about them below. #### Bad solution №1 Bad solution number one is using global instance of `notification`. Ant Design allows using global notification instance. ```ts {7-17} // model.ts import { createEvent, createEffect, sample } from "effector"; import { notification } from "antd"; const dataLoadingFailed = createEvent<{ reason: string }>(); // Create an Effect to show a notification const showWarningFx = createEffect((params: { message: string }) => { notification.warning(params); }); // Execute it when dataLoadingFailed is happened sample({ clock: dataLoadingFailed, fn: ({ reason }) => ({ message: reason }), target: showWarningFx, }); ``` In this solution it is not possible to use any Ant's settings from React Context, because it does not have access to the React at all. It means that notifications will not be styled properly and could look different from the rest of the application. **So, this is not a solution.** #### Bad solution №2 Bad solution number two is using `.watch` method of an event in a component. It is possible to call `.watch` method of an event in a component. ```tsx {9-17} import { useEffect } from "react"; import { notification } from "antd"; import { dataLoadingFailed } from "./model"; function App() { const [api, contextHolder] = notification.useNotification(); useEffect( () => dataLoadingFailed.watch(({ reason }) => { api.warning({ message: reason, }); }), [api], ); return ( <> {contextHolder} {/* ...the rest of the application */} ); } ``` In this solution we do not respect rules for scope, it means that we could have memory leaks, problems with test environments and Storybook-like tools. **So, this is not a solution.** ### Related APIs and Articles * **API** * Scope – Description of scope and its methods * Event – Description of event and its methods * Store – Description of store and its methods * **Articles** * Why you need explicit app start event * Isolated scopes * Testing guide # Migration guide ## Migration guide This guide covers the steps required to migrate to Effector 23 from a previous version. Several features were declared deprecated in this release: * `forward` and `guard` operators * `greedy` option of `sample` was renamed into `batch` * "derived" and "callable" unit types are officially separated now * the ability to use `undefined` as a magic "skip" value in reducers ### Deprecation of `forward` and `guard` Those operators are pretty old and lived through many releases of Effector. But all of their use-cases are already covered by `sample` now, so it is their time to go. You will see a deprecation warning in console for every call of those operators in your code. > TIP: > > You can migrate from both of them by using the official [Effector's ESLint plugin](https://eslint.effector.dev/), which has `no-forward` and `no-guard` rules with built-in [auto-fix feature](https://eslint.org/docs/latest/use/command-line-interface#fix-problems). ### `greedy` to `batch` The `sample` operator had `greedy` option to disable updates batching in rare edge-cases. But the name "greedy" wasn't that obvious for the users, so it is renamed into `batch` and it's signature is reversed. You will see a deprecation warning in console for every usage of `greedy` option in your code. > TIP: > > You can migrate from one to the other by simply running "Find and Replace" from `greedy: true` to `batch: false` in your favorite IDE. ### Separate types for derived and callable units Derived units now fully separated from "callable/writable" ones: * Main factories `createEvent` and `createStore` now return types `EventCallable` and `StoreWritable` (because you can call and write to these units at any moment). * Methods and operators like `unit.map(...)` or `combine(...)` now return types `Event` and `Store`, which are "read-only" i.e. you can only use them as `clock` or `source`, but not as a `target`. * `EventCallable` type is assignable to `Event`, but not the other way around, same for stores. * There are also runtime exceptions for types mismatch. Most likely you will not need to do anything, you will just get better types. But you might have issues with external libraries, **which are not updated to Effector 23 yet**: * Most of the libraries are just *accepting* units as clocks and sources – those cases are ok. * If some operator from the external library is accepting some unit as a `target`, you still will see an good-old `Event` type in this case, so you will not have a type error here even if there is actually an issue. * If some *factory* returns an event, which you are expected to call in your own code, then you will get a type error and you will need to typecast this event to `EventCallable`. > TIP: > > If you run into any of these cases, just create an issue in the repo of this library with a request to support Effector 23 version. > Owners of the project will see relevant type errors in their own source code and tests, once they update Effector in their repo. If you have these issues in your own custom factories or libraries, then you should already see a relevant type errors in the source code of your library. Just replace `Event` with `EventCallable`, `Store` with `StoreWritable` or `Unit` with `UnitTargetable` everywhere it is relevant (i.e. you are going to call or write into these units somehow). ### Magic `undefined` skip is deprecated There is an old feature in Effector: `undefined` is used as a "magic" value to skip updates in reducers in rare cases, e.g. ```ts const $value = createStore(0).on(newValueReceived, (_oldValue, newValue) => newValue); ``` ☝️ if `newValue` is `undefined`, then update will be skipped. The idea of making each mapper and reducer work as a sort of `filterMap` was considered useful in early Effector, but is very rarely used properly, and is confusing and distracting, so it should be deprecated and removed. To do so each and every store factory now supports special `skipVoid` configuration setting, which controls, how specifically store should handle `undefined` value. If set to `false` – store will use `undefined` as a value. If set to `true` (deprecated), store will read `undefined` as a "skip update" command and will do nothing. You will see a warning for each return of undefined in your mappers or reducers in your code, with a requirement to provide an explicit `skipVoid` setting on your store. > TIP: > > If you do want to skip store update in certain cases, then it is better to explicitly return previous state, when possible. It is recommended to use `{skipVoid: false}` at all times, so you are able to use an `undefined` as a normal value. If you do need `undefined` as a "magic skip" value – then you can use `{skipVoid: true}` to preserve current behavior. You still will get a deprecation warning though, but only one for declaration instead of one for every such update. The `skipVoid` setting is temporary and only needed as a way to properly deprecate this feature from Effector. In Effector 24 `skipVoid` itself will be deprecated and then removed. ### `useStore` and `useEvent` to `useUnit` in `effector-react` We merged two old hooks into one, its advantage is that you can pass many units to it at once and it batches all the stores' updates into one single update. It's safe to just swap the calls of the old hooks with the new one: ```ts const Component = () => { const foo = useStore($foo); const bar = useStore($bar); const onSubmit = useEvent(triggerSubmit); }; ``` Becomes: ```ts const Component = () => { const foo = useUnit($foo); const bar = useUnit($bar); const onSubmit = useUnit(triggerSubmit); }; ``` Or shorter: ```ts const Component = () => { const [foo, bar, onSubmit] = useUnit([$foo, $bar, triggerSubmit]); }; ``` # Scope loss import Tabs from "@components/Tabs/Tabs.astro"; import TabItem from "@components/Tabs/TabItem.astro"; import SideBySide from "@components/SideBySide/SideBySide.astro"; ## Scope loss The execution of units in Effector always happens within a scope — either the global one or an isolated one created with fork(). In the global case, the context cannot be lost, since it’s used by default. With an isolated scope, things are trickier: if the scope is lost, operations will start executing in the global mode, and all data updates will **not** enter the scope in which the work was conducted. As a result, an inconsistent state will be sent to the client. Typical places where this happens: * `setTimeout` / `setInterval` * `addEventListener` * WebSocket * direct promise calls inside effects * third-party libraries with async APIs or callbacks. ### Example of the problem We’ll create a simple timer in React, although the same behavior applies to any framework or runtime when scope loss occurs: ```tsx import React from "react"; import { createEvent, createStore, createEffect, scopeBind } from "effector"; import { useUnit } from "effector-react"; const tick = createEvent(); const $timer = createStore(0); $timer.on(tick, (s) => s + 1); export function Timer() { const [timer, startTimer] = useUnit([$timer, startTimerFx]); return (
    Timer:{timer} sec
    ); } ```
    ```tsx import { Provider } from "effector-react"; import { fork } from "effector"; import { Timer } from "./timer"; export const scope = fork(); export default function App() { return ( ); } ```
    Now let’s add an effect that calls `tick` every second: ```ts const startTimerFx = createEffect(() => { setInterval(() => { tick(); }, 1000); }); ``` [Playground](https://codesandbox.io/p/sandbox/nrqw96).
    At first glance, the code looks fine, but if you start the timer, you’ll notice that the UI doesn’t update. This happens because timer changes occur in the global scope, while our app is running in the isolated scope we passed to \. You can observe this in the console. ### How to fix scope loss? To fix scope loss, you need to use the scopeBind function. This method returns a function bound to the scope in which it was called, and can later be safely executed: ```ts ins={2} "bindedTick" const startTimerFx = createEffect(() => { const bindedTick = scopeBind(tick); setInterval(() => { bindedTick(); }, 1000); }); ``` [Updated code example](https://codesandbox.io/p/devbox/scope-loss-forked-vx4r9x?workspaceId=ws_BJxLCP4FhfNzjg1qXth95S). Note that scopeBind automatically works with the currently used scope. However, if needed, you can pass the desired scope explicitly as the second argument: ```ts scopeBind(tick, { scope }); ``` > TIP Clearing Intervals: > > Don’t forget to clear `setInterval` after finishing work to avoid memory leaks. You can handle this with a separate effect, return the interval ID from the first effect and store it in a dedicated store. ### Why does scope loss happen? Let’s illustrate how scope works in effector: ```ts // our active scope let scope; function process() { try { scope = "effector"; asyncProcess(); } finally { scope = undefined; console.log("our scope is undefined now"); } } async function asyncProcess() { console.log("we have scope", scope); // effector await 1; // here we already lost the context console.log("but here scope is gone", scope); // undefined } process(); // Output: // we have scope effector // our scope is undefined now // but here scope is gone undefined ``` You might be wondering **"Is this specifically an Effector problem?"**, but this is a general principle of working with asynchronicity in JavaScript. All technologies that face the need to preserve the context in which calls occur somehow work around this difficulty. The most characteristic example is [zone.js](https://github.com/angular/angular/tree/main/packages/zone.js), which wraps all asynchronous global functions like `setTimeout` or `Promise.resolve` to preserve context. Other solutions to this problem include using generators or `ctx.schedule(() => asyncCall())`. > INFO Future solution: > > JavaScript is preparing a proposal [Async Context](https://github.com/tc39/proposal-async-context), which aims to solve the context loss problem at the language level. This will allow: > > Automatically preserving context through all asynchronous calls > Eliminating the need for explicit use of scopeBind > Getting more predictable behavior of asynchronous code > > Once this proposal enters the language and receives wide support, Effector will be updated to use this native solution. ### Related API and articles * **API** * Effect - Description of effects, their methods and properties * Scope - Description of scopes and their methods * scopeBind - Method for binding a unit to a scope * fork - Operator for creating a scope * allSettled - Method for calling a unit in a given scope and awaiting the full effect chain * **Articles** * Isolated scopes * SSR guide * Guide to testing * The importance of SIDs for store hydration # Server Side Rendering ## Server Side Rendering Server-side rendering (SSR) means that the content of your site is generated on the server and then sent to the browser – which these days is achieved in very different ways and forms. > INFO: > > Generally, if the rendering happens at the runtime – it is called SSR. If the rendering happens at the build-time – it is usually called Server Side Generation (SSG), which in fact is basically a subset of SSR. > > This difference it is not important for this guide, everything said applies both to SSR and SSG. In this guide we will cover two main kinds of Server Side Rendering patterns and how effector should be used in these cases. ### Non-Isomorphic SSR You don't need to do anything special to support non-isomorphic SSR/SSG workflow. This way initial HTML is usually generated separately, by using some sort of template engine, which is quite often run with different (not JS) programming language. The frontend code in this case works only at the client browser and **is not used in any way** to generate the server response. This approach works for effector, as well as any javascript code. Any SPA application is basically an edge-case of it, as its HTML template does not contain any content, except for ` ``` ```jsx import { createEvent, createStore } from "effector"; import { useUnit } from "effector-solid"; import { $counter, incremented, decremented } from "./counter.js"; const Counter = () => { const [counter, onIncremented, onDecremented] = useUnit([$counter, incremented, decremented]); // or const { counter, onIncremented, onDecremented } = useUnit({ $counter, incremented, decremented }); // or const counter = useUnit($counter); const onIncremented = useUnit(incremented); const onDecremented = useUnit(decremented); return (

    Count: {counter()}

    ); }; export default Counter; ```
    > INFO Where is Svelte?: > > Svelte works with the base effector package, so no additional integration is required. ### Additional installation options #### Older browsers For legacy browsers down to IE11 or Chrome 47 (often shipped with Smart TVs) import from the `compat` builds: `effector/compat`, `effector-react/compat`, and `effector-vue/compat`. You can update imports manually: ```ts ins={2} del={1} import { createStore } from "effector"; import { createStore } from "effector/compat"; ``` Or use [babel-plugin-module-resolver](https://github.com/tleunen/babel-plugin-module-resolver). Example `.babelrc` configuration: ```json { "plugins": [ [ "babel-plugin-module-resolver", { "alias": { "^effector$": "effector/compat", "^effector-react$": "effector-react/compat" } } ] ] } ``` #### Other runtimes You can also run effector in environments like `deno`. Import `effector.mjs` directly from a CDN: ```ts import { createStore } from "https://cdn.jsdelivr.net/npm/effector/effector.mjs"; ``` CDN references: * https://www.jsdelivr.com/package/npm/effector * https://cdn.jsdelivr.net/npm/effector/effector.cjs.js * https://cdn.jsdelivr.net/npm/effector/effector.mjs * https://cdn.jsdelivr.net/npm/effector-react/effector-react.cjs.js * https://cdn.jsdelivr.net/npm/effector-vue/effector-vue.cjs.js ### Playgrounds All examples in this documentation run inside [our online playground](https://share.effector.dev). It lets you prototype, test, and share ideas for free, with React and TypeScript support ready to go. The project lives in [this repository](https://github.com/effector/repl). For TypeScript-specific experiments, feel free to [try the official TS playground](https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAbwMZQKYEMaoMo2qgGhQywFEAzc1JGAXznKghDgHJVLq8pWAofoA). # Motivation ## Motivation Modern web application development is becoming more complex every day. Multiple frameworks, complex business logic, different approaches to state management — all of this creates additional challenges for developers. Effector offers an elegant solution to these problems. ### Why Effector? Effector was designed to describe application business logic in a simple and clear language using three basic primitives: * Event — for describing events * Store — for state management * Effect — for handling side effects At the same time, user interface logic is handled by the framework. Let each framework efficiently address its specific task. ### Separation of Concerns In modern development, business logic and user interface are clearly separated: **Business Logic** — is the essence of your application, the reason it exists. It can be complex and based on reactive principles, but it defines how your product works. **UI Logic** — is how users interact with business logic through the interface. These are buttons, forms, and other control elements. ### This is Why Effector! In real projects, tasks from product managers rarely contain interface implementation details. Instead, they describe user interaction scenarios with the system. Effector allows you to describe these scenarios in the same language that the development team uses: * Users interact with the application → Event * See changes on the page → Store * Application interacts with the outside world → Effect ### Framework agnostic Despite React, Angular, and Vue having different approaches to development, application business logic remains unchanged. Effector allows you to describe it uniformly, regardless of the chosen framework. This means you can: 1. Focus on business logic, not framework specifics 2. Easily reuse code between different parts of the application 3. Create more maintainable and scalable solutions # Countdown timer on setTimeout Sometimes we need a simple countdown. The next example allows us to handle each tick and abort the timer. Link to a playground Task: 1. Execute tick every `timeout` milliseconds 2. Each tick should send left seconds to listeners 3. Countdown can be stopped (`abort` argument) 4. Countdown can't be started if already started ```js function createCountdown(name, { start, abort = createEvent(`${name}Reset`), timeout = 1000 }) { // tick every 1 second const $working = createStore(true, { name: `${name}Working` }); const tick = createEvent(`${name}Tick`); const timerFx = createEffect(`${name}Timer`).use(() => wait(timeout)); $working.on(abort, () => false).on(start, () => true); sample({ source: start, filter: timerFx.pending.map((is) => !is), target: tick, }); sample({ clock: tick, target: timerFx, }); const willTick = sample({ source: timerFx.done.map(({ params }) => params - 1), filter: (seconds) => seconds >= 0, }); sample({ source: willTick, filter: $working, target: tick, }); return { tick }; } function wait(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } ``` Usage: ```js const startCountdown = createEvent(); const abortCountdown = createEvent(); const countdown = createCountdown("simple", { start: startCountdown, abort: abortCountdown, }); // handle each tick countdown.tick.watch((remainSeconds) => { console.info("Tick. Remain seconds: ", remainSeconds); }); // let's start startCountdown(15); // 15 ticks to count down, 1 tick per second // abort after 5 second setTimeout(abortCountdown, 5000); ``` # Integrate Next.js There is the official Next.js bindings package - [`@effector/next`](https://github.com/effector/next). Follow its documentation to find out, how to integrate Next.js with effector. # Integrate with Next.js router > TIP: > > There is the official Next.js bindings package - [`@effector/next`](https://github.com/effector/next). Follow its documentation to find out, how to integrate Next.js with effector. This is a simplified example of integration with the Next.js router. We create a similar model for storing the router instance: ```js import { attach, createEvent, createStore, sample } from 'effector' import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime' const routerAttached = createEvent() const navigationTriggered = createEvent() const $router = createStore(null).on( routerAttached, (_, router) => router, ) const navigateFx = attach({ source: $router, effect: (router, path) => { if (!router) return return router.push(path) }, }) sample({ clock: navigationTriggered, target: navigateFx, }) export { navigationTriggered, routerAttached } ``` We make provider: ```js import { useUnit } from 'effector-react'; import { useRouter } from 'next/navigation' export function EffectorRouterProvider({ children }: { children: React.ReactNode }) { const router = useRouter() const attachRouter = useUnit(routerAttached) useEffect(() => { attachRouter(router) }, [router, attachRouter]) return <>{children} } ``` We use provider: ```js import { EffectorRouterProvider } from '@/providers/effector-router-provider'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` And we use it in our models: ```js import { sample } from 'effector'; ... sample({ clock: getUserFx.done, fn: () => '/home', target: navigationTriggered, }); ``` or in components: ```js 'use client'; import { useUnit } from 'effector-react'; import { navigationTriggered } from '@/your-path-name'; ... export function goToSomeRouteNameButton() { const goToSomeRouteName = useUnit(navigationTriggered); return ( ); } ``` # Use scopeBind in Next.js > TIP: > > There is the official Next.js bindings package - [`@effector/next`](https://github.com/effector/next). Follow its documentation to find out, how to integrate Next.js with effector. There are situations when we need to get values from external libraries through callbacks. If we directly bind events, then we will face the loss of the scope. To solve this problem, we can use scopeBind. We have some external library that returns us the status of our connection. Let's call it an instance in the store and call it *$service*, and we will take the status through an event. ```js import { createEvent, createStore } from "effector"; const $connectStatus = createStore("close"); const connectEv = createEvent(); sample({ clock: connectEv, targt: $connectStatus, }); ``` Next, we need to create an effect, within which we will connect our event and *service*. ```js import { attach, scopeBind } from "effector"; const connectFx = attach({ source: { service: $service, }, async effect({ service }) { /** * `scopeBind` will automatically derive current scope, if called inside of an Effect */ const serviceStarted = scopeBind(connectEv); return await service.on("service_start", serviceStarted); }, }); ``` After calling our effect, the event will be tied to the scope and will be able to take the current value from our *service*. # AsyncStorage Counter on React Native The following example is a React Native counter that stores data to AsyncStorage. It uses store, events and effects. ```js import * as React from "react"; import { Text, View, StyleSheet, TouchableOpacity } from "react-native"; import AsyncStorage from "@react-native-community/async-storage"; import { createStore, createEvent, createEffect, sample } from "effector"; import { useUnit } from "effector-react"; const init = createEvent(); const increment = createEvent(); const decrement = createEvent(); const reset = createEvent(); const fetchCountFromAsyncStorageFx = createEffect(async () => { const value = parseInt(await AsyncStorage.getItem("count")); return !isNaN(value) ? value : 0; }); const updateCountInAsyncStorageFx = createEffect(async (count) => { try { await AsyncStorage.setItem("count", `${count}`, (err) => { if (err) console.error(err); }); } catch (err) { console.error(err); } }); const $counter = createStore(0); sample({ clock: fetchCountFromAsyncStorageFx.doneData, target: init, }); $counter .on(init, (state, value) => value) .on(increment, (state) => state + 1) .on(decrement, (state) => state - 1) .reset(reset); sample({ clock: $counter, target: updateCountInAsyncStorageFx, }); fetchCountFromAsyncStorageFx(); export default () => { const count = useUnit(counter); return ( {count} - 0 + ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: "center", paddingTop: 20, backgroundColor: "#ecf0f1", padding: 8, }, paragraph: { margin: 24, fontSize: 60, fontWeight: "bold", textAlign: "center", }, buttons: { flexDirection: "row", alignSelf: "center", justifyContent: "space-between", }, button: { marginHorizontal: 10, paddingVertical: 10, paddingHorizontal: 20, backgroundColor: "#4287f5", borderRadius: 5, }, label: { fontSize: 30, color: "#ffffff", fontWeight: "bold", }, }); ``` # React Counter ```js import React from "react"; import ReactDOM from "react-dom"; import { createEvent, createStore, combine } from "effector"; import { useUnit } from "effector-react"; const plus = createEvent(); const $counter = createStore(1); const $counterText = $counter.map((count) => `current value = ${count}`); const $counterCombined = combine({ counter: $counter, text: $counterText }); $counter.on(plus, (count) => count + 1); function App() { const counter = useUnit($counter); const counterText = useUnit($counterText); const counterCombined = useUnit($counterCombined); return (
    counter: {counter}
    counterText: ${counterText}
    counterCombined: {counterCombined.counter}, {counterCombined.text}
    ); } ReactDOM.render(, document.getElementById("root")); ``` Try it # Dynamic form schema Try it ```js import { createEvent, createEffect, createStore, createApi, sample } from "effector"; import { useList, useUnit } from "effector-react"; const submitForm = createEvent(); const addMessage = createEvent(); const changeFieldType = createEvent(); const showTooltipFx = createEffect(() => new Promise((rs) => setTimeout(rs, 1500))); const saveFormFx = createEffect((data) => { localStorage.setItem("form_state/2", JSON.stringify(data, null, 2)); }); const loadFormFx = createEffect(() => { return JSON.parse(localStorage.getItem("form_state/2")); }); const $fieldType = createStore("text"); const $message = createStore("done"); const $mainForm = createStore({}); const $types = createStore({ username: "text", email: "text", password: "text", }); const $fields = $types.map((state) => Object.keys(state)); $message.on(addMessage, (_, message) => message); $mainForm.on(loadFormFx.doneData, (form, result) => { let changed = false; form = { ...form }; for (const key in result) { const { value } = result[key]; if (value == null) continue; if (form[key] === value) continue; changed = true; form[key] = value; } if (!changed) return; return form; }); const mainFormApi = createApi($mainForm, { upsertField(form, name) { if (name in form) return; return { ...form, [name]: "" }; }, changeField(form, [name, value]) { if (form[name] === value) return; return { ...form, [name]: value }; }, addField(form, [name, value = ""]) { if (form[name] === value) return; return { ...form, [name]: value }; }, deleteField(form, name) { if (!(name in form)) return; form = { ...form }; delete form[name]; return form; }, }); $types.on(mainFormApi.addField, (state, [name, value, type]) => { if (state[name] === type) return; return { ...state, [name]: value }; }); $types.on(mainFormApi.deleteField, (state, name) => { if (!(name in state)) return; state = { ...state }; delete state[name]; return state; }); $types.on(loadFormFx.doneData, (state, result) => { let changed = false; state = { ...state }; for (const key in result) { const { type } = result[key]; if (type == null) continue; if (state[key] === type) continue; changed = true; state[key] = type; } if (!changed) return; return state; }); const changeFieldInput = mainFormApi.changeField.prepend((e) => [ e.currentTarget.name, e.currentTarget.type === "checkbox" ? e.currentTarget.checked : e.currentTarget.value, ]); const submitField = mainFormApi.addField.prepend((e) => [ e.currentTarget.fieldname.value, e.currentTarget.fieldtype.value === "checkbox" ? e.currentTarget.fieldvalue.checked : e.currentTarget.fieldvalue.value, e.currentTarget.fieldtype.value, ]); const submitRemoveField = mainFormApi.deleteField.prepend((e) => e.currentTarget.field.value); $fieldType.on(changeFieldType, (_, e) => e.currentTarget.value); $fieldType.reset(submitField); submitForm.watch((e) => { e.preventDefault(); }); submitField.watch((e) => { e.preventDefault(); e.currentTarget.reset(); }); submitRemoveField.watch((e) => { e.preventDefault(); }); sample({ clock: [submitForm, submitField, submitRemoveField], source: { values: $mainForm, types: $types }, target: saveFormFx, fn({ values, types }) { const form = {}; for (const [key, value] of Object.entries(values)) { form[key] = { value, type: types[key], }; } return form; }, }); sample({ clock: addMessage, target: showTooltipFx, }); sample({ clock: submitField, fn: () => "added", target: addMessage, }); sample({ clock: submitRemoveField, fn: () => "removed", target: addMessage, }); sample({ clock: submitForm, fn: () => "saved", target: addMessage, }); loadFormFx.finally.watch(() => { ReactDOM.render(, document.getElementById("root")); }); function useFormField(name) { const type = useStoreMap({ store: $types, keys: [name], fn(state, [field]) { if (field in state) return state[field]; return "text"; }, }); const value = useStoreMap({ store: $mainForm, keys: [name], fn(state, [field]) { if (field in state) return state[field]; return ""; }, }); mainFormApi.upsertField(name); return [value, type]; } function Form() { const pending = useUnit(saveFormFx.pending); return (

    Form

    {useList($fields, (name) => ( ))} ); } function InputField({ name }) { const [value, type] = useFormField(name); let input = null; switch (type) { case "checkbox": input = ( ); break; case "text": default: input = ; } return ( <> {input} ); } function FieldForm() { const currentFieldType = useUnit($fieldType); const fieldValue = currentFieldType === "checkbox" ? ( ) : ( ); return (

    Insert new field

    {fieldValue}
    ); } function RemoveFieldForm() { return (

    Remove field

    ); } const Tooltip = () => { const [visible, text] = useUnit([showTooltipFx.pending, $message]); return ; }; const App = () => ( <>
    ); await loadFormFx(); css` [data-tooltip]:before { display: block; background: white; width: min-content; content: attr(data-tooltip); position: sticky; top: 0; left: 50%; color: darkgreen; font-family: sans-serif; font-weight: 800; font-size: 20px; padding: 5px 5px; transition: transform 100ms ease-out; } [data-tooltip][data-visible="true"]:before { transform: translate(0px, 0.5em); } [data-tooltip][data-visible="false"]:before { transform: translate(0px, -2em); } [data-form] { display: contents; } [data-form] > header { grid-column: 1 / span 2; } [data-form] > header > h4 { margin-block-end: 0; } [data-form] label { grid-column: 1; justify-self: end; } [data-form] input:not([type="submit"]), [data-form] select { grid-column: 2; } [data-form] input[type="submit"] { grid-column: 2; justify-self: end; width: fit-content; } #app { width: min-content; display: grid; grid-column-gap: 5px; grid-row-gap: 8px; grid-template-columns: repeat(2, 3fr); } `; function css(tags, ...attrs) { const value = style(tags, ...attrs); const node = document.createElement("style"); node.id = "insertedStyle"; node.appendChild(document.createTextNode(value)); const sheet = document.getElementById("insertedStyle"); if (sheet) { sheet.disabled = true; sheet.parentNode.removeChild(sheet); } document.head.appendChild(node); function style(tags, ...attrs) { if (tags.length === 0) return ""; let result = " " + tags[0]; for (let i = 0; i < attrs.length; i++) { result += attrs[i]; result += tags[i + 1]; } return result; } } ``` # Effects with React ```js import React from "react"; import ReactDOM from "react-dom"; import { createEffect, createStore, sample } from "effector"; import { useUnit } from "effector-react"; const url = "https://gist.githubusercontent.com/" + "zerobias/24bc72aa8394157549e0b566ac5059a4/raw/" + "b55eb74b06afd709e2d1d19f9703272b4d753386/data.json"; const loadUserClicked = createEvent(); const fetchUserFx = createEffect((url) => fetch(url).then((req) => req.json())); const $user = createStore(null); sample({ clock: loadUserClicked, fn: () => url, target: fetchUserFx, }); $user.on(fetchUserFx.doneData, (_, user) => user.username); const App = () => { const [user, pending] = useUnit([$user, fetchUserFx.pending]); const handleUserLoad = useUnit(loadUserClicked); return (
    {user ?
    current user: {user}
    :
    no current user
    }
    ); }; ReactDOM.render(, document.getElementById("root")); ``` Try it # Forms ### Example 1 ```jsx import React from "react"; import ReactDOM from "react-dom"; import { createEffect, createStore, createEvent, sample } from "effector"; import { useStoreMap } from "effector-react"; const formSubmitted = createEvent(); const fieldUpdate = createEvent(); const sendFormFx = createEffect((params) => { console.log(params); }); const $form = createStore({}); $form.on(fieldUpdate, (form, { key, value }) => ({ ...form, [key]: value, })); sample({ clock: formSubmitted, source: $form, target: sendFormFx, }); const handleChange = fieldUpdate.prepend((event) => ({ key: event.target.name, value: event.target.value, })); const Field = ({ name, type, label }) => { const value = useStoreMap({ store: $form, keys: [name], fn: (values) => values[name] ?? "", }); return (
    {label}
    ); }; const App = () => ( ); formSubmitted.watch((e) => { e.preventDefault(); }); ReactDOM.render(, document.getElementById("root")); ``` Try it Let's break down the code above. These are just events & effects definitions. ```js const sendFormFx = createEffect((params) => { console.log(params); }); const formSubmitted = createEvent(); // will be used further, and indicates, we have an intention to submit form const fieldUpdate = createEvent(); //has intention to change $form's state in a way, defined in reducer further const $form = createStore({}); $form.on(fieldUpdate, (form, { key, value }) => ({ ...form, [key]: value, })); ``` The next piece of code shows how we can obtain a state in effector in the right way. This kind of state retrieving provides state consistency, and removes any possible race conditions, which can occur in some cases, when using `getState`. ```js sample({ clock: formSubmitted, // when `formSubmitted` is triggered source: $form, // Take LATEST state from $form, and target: sendFormFx, // pass it to `sendFormFx`, in other words -> sendFormFx(state) //fn: (sourceState, clockParams) => transformedData // we could additionally transform data here, but if we need just pass source's value, we may omit this property }); ``` So far, so good, we've almost set up our model (events, effects and stores). Next thing is to create event, which will be used as `onChange` callback, which requires some data transformation, before data appear in `fieldUpdate` event. ```js const handleChange = fieldUpdate.prepend((event) => ({ key: event.target.name, value: event.target.value, })); // upon trigger `handleChange`, passed data will be transformed in a way, described in function above, and returning value will be passed to original `setField` event. ``` Next, we have to deal with how inputs should work. useStoreMap hook here prevents component rerender upon non-relevant changes. ```jsx const Field = ({ name, type, label }) => { const value = useStoreMap({ store: $form, // take $form's state keys: [name], // watch for changes of `name` fn: (values) => values[name] ?? "", // retrieve data from $form's state in this way (note: there will be an error, if undefined is returned) }); return (
    {label}{" "}
    ); }; ``` And, finally, the `App` itself! Note, how we got rid of any business-logic in view layer. It's simpler to debug, to share logic, and even more: logic is framework independent now. ```jsx const App = () => (
    ); ``` Prevent the default html form submit behavior using react event from `submitted`: ```js submitted.watch((e) => { e.preventDefault(); }); ``` ### Example 2 This example demonstrates how to manage state by using an uncontrolled form, handle data loading, create components that depend on stores, and transform data passed between events. ```jsx import React from "react"; import ReactDOM from "react-dom"; import { createEffect, createStore } from "effector"; import { useUnit } from "effector-react"; //defining simple Effect, which results a string in 3 seconds const sendFormFx = createEffect( (formData) => new Promise((rs) => setTimeout(rs, 1000, `Signed in as [${formData.get("name")}]`)), ); const Loader = () => { //typeof loading === "boolean" const loading = useUnit(sendFormFx.pending); return loading ?
    Loading...
    : null; }; const SubmitButton = (props) => { const loading = useUnit(sendFormFx.pending); return ( ); }; //transforming upcoming data, from DOM Event to FormData const onSubmit = sendFormFx.prepend((e) => new FormData(e.target)); const App = () => { const submit = useUnit(onSubmit); return (
    { e.preventDefault(); submit(e); }} > Login:
    Password:
    ); }; ReactDOM.render(, document.getElementById("root")); ``` Try it # Gate Gate is a bridge between props and stores. Imagine you have the task of transferring something from React props to the effector store. Suppose you pass the history object from the react-router to the store, or pass some callbacks from render-props. In a such situation Gate will help. ```js import { createStore, createEffect, sample } from "effector"; import { useUnit, createGate } from "effector-react"; // Effect for api request const getTodoFx = createEffect(async ({ id }) => { const req = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`); return req.json(); }); // Our main store const $todo = createStore(null); const TodoGate = createGate(); $todo.on(getTodoFx.doneData, (_, todo) => todo); // We call getTodoFx effect every time Gate updates its state. sample({ clock: TodoGate.state, target: getTodoFx }); TodoGate.open.watch(() => { //called each time when TodoGate is mounted }); TodoGate.close.watch(() => { //called each time when TodoGate is unmounted }); function Todo() { const [todo, loading] = useUnit([$todo, getTodoFx.pending]); if (loading) { return
    Loading...
    ; } if (!todo || Object.keys(todo).length === 0) { return
    empty
    ; } return (

    title: {todo.title}

    id: {todo.id}

    ); } const App = () => { // value which need to be accessed outside from react const [id, setId] = React.useState(0); return ( <> {/*In this situation, we have the ability to simultaneously render a component and make a request, rather than wait for the component*/} ); }; ReactDOM.render(, document.getElementById("root")); ``` Try it # Slots A slot is a place in a component where you can insert any unknown component. It's a well-known abstraction used by frameworks such as Vue.js and Svelte. Slots aren't present in the React. With React, you can achieve this goal using props or `React.Context`. In large projects, this is not convenient, because it generates "props hell" or smears the logic. Using React with effector, we can achieve slot goals without the problems described above. * [Slots proposal](https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Slots-Proposal) * [Vue.js docs](https://v3.vuejs.org/guide/component-slots.html) * [Svelte docs](https://svelte.dev/docs#slot) * [@space307/effector-react-slots](https://github.com/space307/effector-react-slots) [Open ReplIt](https://replit.com/@binjospookie/effector-react-slots-example) ```tsx import { createApi, createStore, createEvent, sample, split } from "effector"; import { useStoreMap } from "effector-react"; import React from "react"; import type { ReactElement, PropsWithChildren } from "react"; type Component = (props: PropsWithChildren) => ReactElement | null; type Store = { readonly component: Component; }; function createSlotFactory({ slots }: { readonly slots: Record }) { const api = { remove: createEvent<{ readonly id: Id }>(), set: createEvent<{ readonly id: Id; readonly component: Component }>(), }; function createSlot

    ({ id }: { readonly id: Id }) { const defaultToStore: Store

    = { component: () => null, }; const $slot = createStore>(defaultToStore); const slotApi = createApi($slot, { remove: (state) => ({ ...state, component: defaultToStore.component }), set: (state, payload: Component

    ) => ({ ...state, component: payload }), }); const isSlotEventCalling = (payload: { readonly id: Id }) => payload.id === id; sample({ clock: api.remove, filter: isSlotEventCalling, target: slotApi.remove, }); sample({ clock: api.set, filter: isSlotEventCalling, fn: ({ component }) => component, target: slotApi.set, }); function Slot(props: P = {} as P) { const Component = useStoreMap({ store: $slot, fn: ({ component }) => component, keys: [], }); return ; } return { $slot, }; } return { api, createSlot, }; } const SLOTS = { FOO: "foo" } as const; const { api, createSlot } = createSlotFactory({ slots: SLOTS }); const { Slot: FooSlot } = createSlot({ id: SLOTS.FOO }); const ComponentWithSlot = () => ( <>

    Hello, Slots!

    ); const updateFeatures = createEvent(""); const $featureToggle = createStore(""); const MyAwesomeFeature = () =>

    Look at my horse

    ; const VeryAwesomeFeature = () =>

    My horse is amaizing

    ; $featureToggle.on(updateFeatures, (_, feature) => feature); split({ source: $featureToggle, match: { awesome: (data) => data === "awesome", veryAwesome: (data) => data === "veryAwesome", hideAll: (data) => data === "hideAll", }, cases: { awesome: api.set.prepend(() => ({ id: SLOTS.FOO, component: MyAwesomeFeature, })), veryAwesome: api.set.prepend(() => ({ id: SLOTS.FOO, component: VeryAwesomeFeature, })), hideAll: api.remove.prepend(() => ({ id: SLOTS.FOO })), }, }); // updateFeatures('awesome'); // render MyAwesomeFeature in slot // updateFeatures('veryAwesome'); // render VeryAwesomeFeature in slot // updateFeatures('hideAll'); // render nothing in slot ``` # ToDo creator Try it ```tsx import React from "react"; import ReactDOM from "react-dom"; import { createStore, createEvent, sample } from "effector"; import { useUnit, useList } from "effector-react"; function createTodoListApi(initial: string[] = []) { const insert = createEvent(); const remove = createEvent(); const change = createEvent(); const reset = createEvent(); const $input = createStore(""); const $todos = createStore(initial); $input.on(change, (_, value) => value); $input.reset(insert); $todos.on(insert, (todos, newTodo) => [...todos, newTodo]); $todos.on(remove, (todos, index) => todos.filter((_, i) => i !== index)); $input.reset(reset); const submit = createEvent(); submit.watch((event) => event.preventDefault()); sample({ clock: submit, source: $input, target: insert, }); return { submit, remove, change, reset, $todos, $input, }; } const firstTodoList = createTodoListApi(["hello, world!"]); const secondTodoList = createTodoListApi(["hello, world!"]); function TodoList({ label, model }) { const input = useUnit(model.$input); const todos = useList(model.$todos, (value, index) => (
  • {value}{" "}
  • )); return ( <>

    {label}

      {todos}
    model.change(event.currentTarget.value)} />
    ); } function App() { return ( <> ); } ReactDOM.render(, document.getElementById("root")); ``` # TODO list with input validation Try it ```js import { createEvent, createStore, createEffect, restore, combine, sample } from "effector"; import { useUnit, useList } from "effector-react"; const submit = createEvent(); const submitted = createEvent(); const completed = createEvent(); const changed = createEvent(); const removed = createEvent(); const validateFx = createEffect(([todo, todos]) => { if (todos.some((item) => item.text === todo)) throw "This todo is already on the list"; if (!todo.trim().length) throw "Required field"; return null; }); const $todo = createStore(""); const $todos = createStore([]); const $error = createStore(""); $todo.on(changed, (_, todo) => todo); $error.reset(changed); $todos.on(completed, (list, index) => list.map((todo, foundIndex) => ({ ...todo, completed: index === foundIndex ? !todo.completed : todo.completed, })), ); $todos.on(removed, (state, index) => state.filter((_, i) => i !== index)); sample({ clock: submit, source: [$todo, $todos], target: validateFx, }); sample({ clock: validateFx.done, source: $todo, target: submitted, }); $todos.on(submitted, (list, text) => [...list, { text, completed: false }]); $todo.reset(submitted); $error.on(validateFx.failData, (_, error) => error); submit.watch((e) => e.preventDefault()); const App = () => { const [todo, error] = useUnit([$todo, $error]); const list = useList($todos, (todo, index) => (
  • completed(index)} /> {todo.text}
  • )); return (

    Todos

    changed(e.target.value)} /> {error &&
    {error}
    }
      {list}
    ); }; ReactDOM.render(, document.getElementById("root")); ``` # Explicit app start ## Explicit app start In Effector events can not be triggered implicitly. It gives you more control over the app's lifecycle and helps to avoid unexpected behavior. ### The code In the simplest case, you can just create something like `appStarted` event and trigger it right after the app initialization. Let us pass through the code line by line and explain what's going on here. 1. Create start event This event will be used to trigger the start of the app. For example, you can attach some global listeners after this it. ```ts ins={3} import { createEvent, fork, allSettled } from 'effector'; const appStarted = createEvent(); const scope = fork(); await allSettled(appStarted, { scope }); ``` 2. Create isolated scope Fork API allows you to create scope which will be used across the app. It helps you to prevent using global stateand avoid unexpected behavior. ```ts ins={5} import { createEvent, fork, allSettled } from 'effector'; const appStarted = createEvent(); const scope = fork(); await allSettled(appStarted, { scope }); ``` 3. Trigger `appStarted` event on the patricular scope. allSettled function allows you to start an event on particular scope and wait until all computations will be finished. ```ts ins={7} import { createEvent, fork, allSettled } from 'effector'; const appStarted = createEvent(); const scope = fork(); await allSettled(appStarted, { scope }); ``` ### The reasons The main reason for this approach is it allows you to control the app's lifecycle. It helps you to avoid unexpected behavior and make your app more predictable in some cases. Let us say we have a module with the following code: ```ts // app.ts import { createStore, createEvent, sample, scopeBind } from 'effector'; const $counter = createStore(0); const increment = createEvent(); const startIncrementationIntervalFx = createEffect(() => { const boundIncrement = scopeBind(increment, { safe: true }); setInterval(() => { boundIncrement(); }, 1000); }); sample({ clock: increment, source: $counter, fn: (counter) => counter + 1, target: $counter, }); startIncrementationIntervalFx(); ``` #### Tests We believe that any serious application has to be testable, so we have to isolate application lifecycle inside particular test-case. In case of implicit start (start of model logic by module execution), it will be impossible to test the app's behavior in different states. > INFO about scopeBind: > > scopeBind function allows you to bind an event to particular scope, more details you can find on the Isolated scopes page, as well as in Scope loss. Now, to test the app's behavior, we have to mock `setInterval` function and check that `$counter` value is correct after particular time. ```ts // app.test.ts import { $counter } from './app'; test('$counter should be 5 after 5 seconds', async () => { // ... test }); test('$counter should be 10 after 10 seconds', async () => { // ... test }); ``` But, counter will be started immediately after the module execution, and we will not be able to test the app's behavior in different states. #### SSR In case of SSR, we have to start all application's logic on every user's request, and it will be impossible to do with implicit start. ```ts // server.ts import * as app from './app'; function handleRequest(req, res) { // ... } ``` But, counter will be started immediately after the module execution (aka application initialization), and we will not be able to start the app's logic on every user's request. #### Add explicit start Let us rewrite the code and add explicit start of the app. ```ts del={22} ins={24-28} // app.ts import { createStore, createEvent, sample, scopeBind } from 'effector'; const $counter = createStore(0); const increment = createEvent(); const startIncrementationIntervalFx = createEffect(() => { const boundIncrement = scopeBind(increment, { safe: true }); setInterval(() => { boundIncrement(); }, 1000); }); sample({ clock: increment, source: $counter, fn: (counter) => counter + 1, target: $counter, }); startIncrementationIntervalFx(); const appStarted = createEvent(); sample({ clock: appStarted, target: startIncrementationIntervalFx, }); ``` That is it! Now we can test the app's behavior in different states and start the app's logic on every user's request. > TIP Don’t stop at start: > > In real-world applications, it is better to add not only explicit start of the app, but also explicit stop of the app. It will help you to avoid memory leaks and unexpected behavior. > You can also apply the same principle to individual features to control each feature’s lifecycle separately. In this recipe, we used application-wide `appStarted` event to trigger the start of the app. However, in real-world applications, it is better to use more granular events to trigger the start of the particular part of the app. ### Related APIs and Articles * **API** * Scope – Description of scope and its methods * scopeBind – Method for binding a unit to a scope * fork – Operator for creating a scope * allSettled – Method for running a unit in a given scope and waiting for the entire chain of effects to complete * **Articles** * What is scope loss and how to fix it * SSR guide * Testing guide * How to Think in the Effector Paradigm # How to think in the effector paradigm import Tabs from "@components/Tabs/Tabs.astro"; import TabItem from "@components/Tabs/TabItem.astro"; ## How to think in the effector paradigm In fact, effector is not only about managing application state, but also about scalable building of your application logic. Effector does not limit you in how you write code, however, if you understand the following principles, it will be much easier to write code and think when you use effector: * Events are the description of your application, the foundation of everything. * Business logic and UI are different things; you should strive to separate the responsibility between data and its display. ### Events — the foundation of everything Every user interaction with your application is an event. An event doesn't decide what should happen, it simply records the fact that something happened, for example: the user submitted a form - `formSubmitted`, the user clicked the refresh button - `refreshButtonClicked`, the user changed the search filter - `searchFilterChanged`, and so on. At the same time, events are not limited to user actions, they can also describe the logic of your model, for example: an explicit start of your model's operation (micro-frontend or feature) - , an error occurred - `errorOccurred`, and so on. Don't hesitate to create as many events as needed to fully describe the application's actions, this makes it easier to see and track how your application works. When designing new functionality, it's easiest to start with events, since they are immediately visible in the interface. > TIP Use meaningful names: > > Give events meaningful names. For example, if you need to load data upon some action, the event should be related to the action, not the implementation: > > ```ts > ❌ const fetchData = createEvent() > ✅ const appStarted = createEvent() > ``` ### Separate business logic and UI Effector allows you to separate the display (UI) and the logic of your application (business logic). All the logic of your application's operation, as a rule, should be described separately from your UI, in a separate module, for example `model.ts`, and expose outward for the UI only what is needed for display or user interaction. For example, when the `formSubmitted` event is triggered, you can call an effect to send data to the server, another effect to send analytics, and also display a notification to the user when event triggered: ```ts const formSubmitted = createEvent(); const sendFormDataFx = createEffect(() => {}); const sendAnalyticsFx = createEffect(() => {}); const showNotificationFx = createEffect(() => {}); sample({ clock: formSubmitted, target: [sendFormDataFx, sendAnalyticsFx, showNotificationFx], }); ``` At some point, your logic might change, and you decide to send analytics only after a successful form submission, and show the notification not only on form submission but also on error: ```ts const formSubmitted = createEvent(); const sendFormDataFx = createEffect(() => {}); const sendAnalyticsFx = createEffect(() => {}); const showNotificationFx = createEffect(() => {}); sample({ clock: formSubmitted, target: [sendFormDataFx, showNotificationFx], }); sample({ clock: sendFormDataFx.doneData, target: sendAnalyticsFx, }); sample({ clock: sendFormDataFx.failData, target: showNotificationFx, }); ``` Our application logic has changed, but the UI has not. Our UI doesn't need to know which effects we are sending or what is changing, all our UI knows is that the refresh button was clicked and it needs to trigger the `refreshButtonClicked` event. Otherwise, if we mix logic and UI, we will have to change the code in the UI as well when the logic changes. ### How does this look in a real application? Let's take GitHub and its repository functionality as an example. Every user action is an event: ![repository action buttons in github](/images/github-repo-actions.png) * User starred/unstarred the repository - `repoStarToggled` * User changed the repository branch - `repoBranchChanged` * Repository file search string changed - `repoFileSearchChanged` * Repository was forked - `repoForked` It's much easier to build the entire application logic around events and the reactions to them. The UI simply reports an action, and its processing is part of the business logic. A simplified example of the logic with the star button: ```ts // repo.model.ts // event – the fact of an action const repoStarToggled = createEvent(); // effects as an additional reaction to events // (let's assume the effects return the updated value) const starRepoFx = createEffect(() => {}); const unstarRepoFx = createEffect(() => {}); // application state const $isRepoStarred = createStore(false); const $repoStarsCount = createStore(0); // star toggle logic sample({ clock: repoStarToggled, source: $isRepoStarred, fn: (isRepoStarred) => !isRepoStarred, target: $isRepoStarred, }); // sending a request to the server when the star is toggled sample({ clock: $isRepoStarred, filter: (isRepoStarred) => isRepoStarred, target: starRepoFx, }); sample({ clock: $isRepoStarred, filter: (isRepoStarred) => !isRepoStarred, target: unstarRepoFx, }); // update the counter sample({ clock: [starRepoFx.doneData, unstarRepoFx.doneData], target: $repoStarsCount, }); ``` ```tsx import { repoStarToggled, $isRepoStarred, $repoStarsCount } from "./repo.model.ts"; const RepoStarButton = () => { const [onStarToggle, isRepoStarred, repoStarsCount] = useUnit([ repoStarToggled, $isRepoStarred, $repoStarsCount, ]); return (
    {repoStarsCount}
    ); }; ```
    ### Related API and articles * **API** * Event - Description of an event and its methods * Store - Description of a store and its methods * Effect - Description of an effect and its methods * **Articles** * Why you need an explicit start event for your application * How to manage state * Working with events * How to react to events in the UI # Releases policy ## Releases policy The main goal of effector is to **make developer experience better**, as a part of this strategy we are committing to some rules of effector releases. ### No breaking changes without prior deprecation Before each breaking change, the effector must provide a deprecation warning for **at least a year before.** For example: * When version 22 was released, feature "A" was marked as deprecated. The library gives a warning to the console when it is used. * A year later, in version 23 release, feature "A" is removed. ### Release cycle Major updates (i.e. with breaking changes) of the effector are released **no more than once a year.** Minor and patch updates (i.e., with fixes and new features) are released when ready. If a new feature requires breaking changes – it is also released in a major update. This is necessary to allow developers to plan their work smoothly, taking into account possible changes in effector. It also obliges effector maintainers to be extremely careful when designing new features and breaking changes to old library features, because the opportunity to remove or heavily modify something in the public API only appears once every two years. # Unit Initialization ## Unit initialization When working with effector, it is important to follow a key rule — create units statically at the module level, not at runtime, to avoid memory leaks in your application. To understand why this happens, we need to look into the effector core and see how it works. At its foundation lies a [graph](https://en.wikipedia.org/wiki/Graph_\(discrete_mathematics\)) model. Each unit is a node in the graph, and each node stores information about state, operations, and links to other units. For example, in the following code: ```ts import { combine, createStore } from "effector"; const $store = createStore(0); const $derivedStore = combine($store, (storeVal) => !!storeVal); ``` When creating a `$store`, a new node is added to Effector’s graph, which holds a reference to the store. For a derived store, a node is also created along with a connection to the source store. You can verify this by logging the source store to the console, expanding its `graphite.next` property (an array links to the next nodes), finding the node where `meta.op` is `combine`, and then expanding its `next` — that will be your derived store. Since references to unit objects are preserved in the graph, [GC](https://javascript.info/garbage-collection) in JavaScript is not able to remove them from memory. Therefore, for example, if you create units or connections between them inside a React component, they will be recreated on every component mount, while the old units will still remain alive and functioning. ### What about dynamics? Of course, the effector team understands how important dynamic behavior is, which is why dynamic models are now under active development and are expected in the next major release! # Usage with effector-react ## Usage with React **TypeScript** is a typed superset of JavaScript. It became popular recently in applications due to the benefits it can bring. If you are new to TypeScript, it is highly recommended to become familiar with it first, before proceeding. You can check out its documentation [here](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html). TypeScript has a potential to bring the following benefits to application: 1. Type safety for state, stores and events 2. Easy refactoring of typed code 3. A superior developer experience in a team environment **A Practical Example** We will be going through a simplistic chat application to demonstrate a possible approach to include static typing. This chat application will have API mock that load and saves data from localStorage. The full source code is available on [github](https://github.com/effector/effector/tree/master/examples/react-and-ts). Note that, by going through this example yourself, you will experience some benefits of using TypeScript. ### Let's create API mock There is a directory structure inherited from the [feature-sliced](https://feature-sliced.github.io/documentation/) methodology. Let's define a simple type, that our improvised API will return. ```ts // File: /src/shared/api/message.ts interface Author { id: string; name: string; } export interface Message { id: string; author: Author; text: string; timestamp: number; } ``` Our API will load and save data to `localStorage`, and we need some functions to load data: ```ts // File: /src/shared/api/message.ts const LocalStorageKey = "effector-example-history"; function loadHistory(): Message[] | void { const source = localStorage.getItem(LocalStorageKey); if (source) { return JSON.parse(source); } return undefined; } function saveHistory(messages: Message[]) { localStorage.setItem(LocalStorageKey, JSON.stringify(messages)); } ``` I also created some libraries to generate identifiers and wait to simulate network requests. ```ts // File: /src/shared/lib/oid.ts export const createOid = () => ((new Date().getTime() / 1000) | 0).toString(16) + "xxxxxxxxxxxxxxxx".replace(/[x]/g, () => ((Math.random() * 16) | 0).toString(16)).toLowerCase(); ``` ```ts // File: /src/shared/lib/wait.ts export function wait(timeout = Math.random() * 1500) { return new Promise((resolve) => setTimeout(resolve, timeout)); } ``` OK. Now we can create effects that will load messages. ```ts // File: /src/shared/api/message.ts // Here effect defined with static types. void defines no arguments. // Second type argument defines a successful result type. // Third argument is optional and defines a failure result type. export const messagesLoadFx = createEffect(async () => { const history = loadHistory(); await wait(); return history ?? []; }); interface SendMessage { text: string; author: Author; } // But we can use type inferring and set arguments types in the handler defintion. // Hover your cursor on `messagesLoadFx` to see the inferred types: // `Effect<{ text: string; authorId: string; authorName: string }, void, Error>` export const messageSendFx = createEffect(async ({ text, author }: SendMessage) => { const message: Message = { id: createOid(), author, timestamp: Date.now(), text, }; const history = await messagesLoadFx(); saveHistory([...history, message]); await wait(); }); // Please, note that we will `wait()` for `messagesLoadFx` and `wait()` in the current effect // Also, note that `saveHistory` and `loadHistory` can throw exceptions, // in that case effect will trigger `messageDeleteFx.fail` event. export const messageDeleteFx = createEffect(async (message: Message) => { const history = await messagesLoadFx(); const updated = history.filter((found) => found.id !== message.id); await wait(); saveHistory(updated); }); ``` OK, now we are done with the messages, let's create effects to manage user session. Really, I prefer to start design code from implementing interfaces: ```ts // File: /src/shared/api/session.ts // It is called session because it describes current user session, not the User at all. export interface Session { id: string; name: string; } ``` Also, to generate usernames and don't require to type it by themselves, import `unique-names-generator`: ```ts // File: /src/shared/api/session.ts import { uniqueNamesGenerator, Config, starWars } from "unique-names-generator"; const nameGenerator: Config = { dictionaries: [starWars] }; const createName = () => uniqueNamesGenerator(nameGenerator); ``` Let's create effects to manage session: ```ts // File: /src/shared/api/session.ts const LocalStorageKey = "effector-example-session"; // Note, that we need explicit types definition in that case, because `JSON.parse()` returns `any` export const sessionLoadFx = createEffect(async () => { const source = localStorage.getItem(LocalStorageKey); await wait(); if (!source) { return null; } return JSON.parse(source); }); // By default, if there are no arguments, no explicit type arguments, and no return statement provided // effect will have type: `Effect` export const sessionDeleteFx = createEffect(async () => { localStorage.removeItem(LocalStorageKey); await wait(); }); // Look at the type of the `sessionCreateFx` constant. // It will be `Effect` because TypeScript can infer type from `session` constant export const sessionCreateFx = createEffect(async () => { // I explicitly set type for the next constant, because it allows TypeScript help me // If I forgot to set property, I'll see error in the place of definition // Also it allows IDE to autocomplete property names const session: Session = { id: createOid(), name: createName(), }; localStorage.setItem(LocalStorageKey, JSON.stringify(session)); return session; }); ``` How we need to import these effects? I surely recommend writing short imports and using reexports. It allows to securely refactor code structure inside `shared/api` and the same slices, and don't worry about refactoring other imports and unnecessary changes in the git history. ```ts // File: /src/shared/api/index.ts export * as messageApi from "./message"; export * as sessionApi from "./session"; // Types reexports made just for convenience export type { Message } from "./message"; export type { Session } from "./session"; ``` ### Create a page with the logic Typical structure of the pages: ``` src/ pages/ / page.tsx — just the View layer model.ts — a business-logic code index.ts — reexports, sometimes there will be a connection-glue code ``` I recommend writing code in the view layer from the top to bottom, more common code at the top. Let's model our view layer. We will have two main sections at the page: messages history and a message form. ```tsx // File: /src/pages/chat/page.tsx export function ChatPage() { return (
    ); } function ChatHistory() { return (
    There will be messages list
    ); } function MessageForm() { return (
    There will be message form
    ); } ``` OK. Now we know what kind of structure we have, and we can start to model business-logic processes. The view layer should do two tasks: render data from stores and report events to the model. The view layer doesn't know how data are loaded, how it should be converted and sent back. ```ts // File: /src/pages/chat/model.ts import { createEvent, createStore } from "effector"; // And the events report just what happened export const messageDeleteClicked = createEvent(); export const messageSendClicked = createEvent(); export const messageEnterPressed = createEvent(); export const messageTextChanged = createEvent(); export const loginClicked = createEvent(); export const logoutClicked = createEvent(); // At the moment, there is just raw data without any knowledge how to load export const $loggedIn = createStore(false); export const $userName = createStore(""); export const $messages = createStore([]); export const $messageText = createStore(""); // Page should NOT know where the data came from. // That's why we just reexport them. // We can rewrite this code to `combine` or independent store, // page should NOT be changed, just because we changed the implementation export const $messageDeleting = messageApi.messageDeleteFx.pending; export const $messageSending = messageApi.messageSendFx.pending; ``` Now we can implement components. ```tsx // File: /src/pages/chat/page.tsx import { useList, useUnit } from "effector-react"; import * as model from "./model"; // export function ChatPage { ... } function ChatHistory() { const [messageDeleting, onMessageDelete] = useUnit([ model.$messageDeleting, model.messageDeleteClicked, ]); // Hook `useList` allows React not rerender messages really doesn't changed const messages = useList(model.$messages, (message) => (

    From: {message.author.name}

    {message.text}

    )); // We don't need `useCallback` here because we pass function to an HTML-element, not a custom component return
    {messages}
    ; } ``` I split `MessageForm` to the different components, to simplify code: ```tsx // File: /src/pages/chat/page.tsx function MessageForm() { const isLogged = useUnit(model.$loggedIn); return isLogged ? : ; } function SendMessage() { const [userName, messageText, messageSending] = useUnit([ model.$userName, model.$messageText, model.$messageSending, ]); const [handleLogout, handleTextChange, handleEnterPress, handleSendClick] = useUnit([ model.logoutClicked, model.messageTextChanged, model.messageEnterPressed, model.messageSendClicked, ]); const handleKeyPress = (event: React.KeyboardEvent) => { if (event.key === "Enter") { handleEnterPress(); } }; return (

    {userName}

    handleTextChange(event.target.value)} onKeyPress={handleKeyPress} className="chat-input" placeholder="Type a message..." />
    ); } function LoginForm() { const handleLogin = useUnit(model.loginClicked); return (
    Please, log in to be able to send messages
    ); } ``` ### Manage user session like a Pro Let's create a session entity. An entity is a business unit. ```ts // File: /src/entities/session/index.ts import { Session } from "shared/api"; import { createStore } from "effector"; // Entity just stores session and some internal knowledge about it export const $session = createStore(null); // When store `$session` is updated, store `$isLogged` will be updated too // They are in sync. Derived store are depends on data from original. export const $isLogged = $session.map((session) => session !== null); ``` Now we can implement login or logout features on the page. Why not here? If we place login logic here, we will have a very implicit scenario, when you call `sessionCreateFx` you won't see code called after effect. But consequences will be visible in the DevTools and application behaviour. Try to write the code in as obvious a way as possible in one file, so that you and any teammate can trace the sequence of execution. ### Implement logic OK. Now we can load a user session and the messages lists on the page mount. But, we don't have any event when we can start. Let's fix it. You can use Gate, but I prefer to use explicit events. ```ts // File: /src/pages/chat/model.ts // Just add a new event export const pageMounted = createEvent(); ``` Just add `useEffect` and call bound event inside. ```tsx // File: /src/pages/chat/page.tsx export function ChatPage() { const handlePageMount = useUnit(model.pageMounted); React.useEffect(() => { handlePageMount(); }, [handlePageMount]); return (
    ); } ``` > Note: if you don't plan to write tests for effector code and/or implement SSR you can omit any usage of `useEvent`. At the moment we can load a session and the messages list. Just add reaction to the event, and any other code should be written in chronological order after each event: ```ts // File: /src/pages/chat/model.ts // Don't forget to import { sample } from "effector" import { Message, messageApi, sessionApi } from "shared/api"; import { $session } from "entities/session"; // export stores // export events // Here the logic place // You can read this code like: // When page mounted, call messages load and session load simultaneously sample({ clock: pageMounted, target: [messageApi.messagesLoadFx, sessionApi.sessionLoadFx], }); ``` After that we need to define reactions on `messagesLoadFx.done` and `messagesLoadFx.fail`, and the same for `sessionLoadFx`. ```ts // File: /src/pages/chat/model.ts // `.doneData` is a shortcut for `.done`, because `.done` returns `{ params, result }` // Do not name your arguments like `state` or `payload` // Use explicit names of the content they contain $messages.on(messageApi.messagesLoadFx.doneData, (_, messages) => messages); $session.on(sessionApi.sessionLoadFx.doneData, (_, session) => session); ``` OK. Session and messages loaded. Let's allow the users to log in. ```ts // File: /src/pages/chat/model.ts // When login clicked we need to create a new session sample({ clock: loginClicked, target: sessionApi.sessionCreateFx, }); // When session created, just write it to a session store sample({ clock: sessionApi.sessionCreateFx.doneData, target: $session, }); // If session create is failed, just reset the session sample({ clock: sessionApi.sessionCreateFx.fail, fn: () => null, target: $session, }); ``` Now we'll implement a logout process: ```ts // File: /src/pages/chat/model.ts // When logout clicked we need to reset session and clear our storage sample({ clock: logoutClicked, target: sessionApi.sessionDeleteFx, }); // In any case, failed or not, we need to reset session store sample({ clock: sessionApi.sessionDeleteFx.finally, fn: () => null, target: $session, }); ``` > Note: most of the comments wrote just for educational purpose. In real life, application code will be self-describable But if we start the dev server and try to log in, we see nothing changed. This is because we created `$loggedIn` store in the model, but don't change it. Let's fix: ```ts // File: /src/pages/chat/model.ts import { $isLogged, $session } from "entities/session"; // At the moment, there is just raw data without any knowledge how to load export const $loggedIn = $isLogged; export const $userName = $session.map((session) => session?.name ?? ""); ``` Here we just reexported our custom store from the session entity, but our View layer doesn't change. The same situation with `$userName` store. Just reload the page, and you'll see, that session loaded correctly. ### Send message Now we can log in and log out. I think you want to send a message. This is pretty simple: ```ts // File: /src/pages/chat/model.ts $messageText.on(messageTextChanged, (_, text) => text); // We have two different events to send message // Let event `messageSend` react on any of them const messageSend = merge([messageEnterPressed, messageSendClicked]); // We need to take a message text and author info then send it to the effect sample({ clock: messageSend, source: { author: $session, text: $messageText }, target: messageApi.messageSendFx, }); ``` But if in the `tsconfig.json` you set `"strictNullChecks": true`, you will see the error there. It is because store `$session` contains `Session | null` and `messageSendFx` wants `Author` in the arguments. `Author` and `Session` are compatible, but not the `null`. To fix this strange behaviour, we need to use `filter` there: ```ts // File: /src/pages/chat/model.ts sample({ clock: messageSend, source: { author: $session, text: $messageText }, filter: (form): form is { author: Session; text: string } => { return form.author !== null; }, target: messageApi.messageSendFx, }); ``` I want to focus your attention on the return type `form is {author: Session; text: string}`. This feature called [type guard](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards) and allows TypeScript to reduce `Session | null` type to more specific `Session` via condition inside the function. Now we can read this like: when a message should be sent, take session and message text, check that session exists, and send it. OK. Now we can write a new message to a server. But if we don't call `messagesLoadFx` again we didn't see any changes, because `$messages` store didn't update. We can write generic code for this case. The easiest way is to return the sent message from the effect. ```ts // File: /src/shared/api/message.ts export const messageSendFx = createEffect(async ({ text, author }: SendMessage) => { const message: Message = { id: createOid(), author, timestamp: Date.now(), text, }; const history = await messagesLoadFx(); await wait(); saveHistory([...history, message]); return message; }); ``` Now we can just append a message to the end of the list: ```ts // File: /src/pages/chat/model.ts $messages.on(messageApi.messageSendFx.doneData, (messages, newMessage) => [ ...messages, newMessage, ]); ``` But at the moment, sent a message still left in the input. ```ts // File: /src/pages/chat/model.ts $messageText.on(messageSendFx, () => ""); // If message sending is failed, just restore the message sample({ clock: messageSendFx.fail, fn: ({ params }) => params.text, target: $messageText, }); ``` ### Deleting the message It is pretty simple. ```ts // File: /src/pages/chat/model.ts sample({ clock: messageDeleteClicked, target: messageApi.messageDeleteFx, }); $messages.on(messageApi.messageDeleteFx.done, (messages, { params: toDelete }) => messages.filter((message) => message.id !== toDelete.id), ); ``` But you can see the bug, when "Deleting" state doesn't disable. This is because `useList` caches renders, and doesn't know about dependency on `messageDeleting` state. To fix it, we need to provide `keys`: ```tsx // File: /src/pages/chat/page.tsx const messages = useList(model.$messages, { keys: [messageDeleting], fn: (message) => (

    From: {message.author.name}

    {message.text}

    ), }); ``` ### Conclusion This is a simple example of an application on effector with React and TypeScript. You can clone this [effector/examples/react-and-ts](https://github.com/effector/effector/tree/master/examples/react-and-ts) and run this example on your computer.