From b4e0302a7797921c947e6eb5eb8adaac871bc98c Mon Sep 17 00:00:00 2001 From: David Maskasky Date: Fri, 24 Jan 2025 20:30:42 -0800 Subject: [PATCH] doc: update jotai-effect docs (#2956) --- docs/extensions/effect.mdx | 180 +++++++++++++++++++++---------------- 1 file changed, 105 insertions(+), 75 deletions(-) diff --git a/docs/extensions/effect.mdx b/docs/extensions/effect.mdx index 413d5f6759..8735aad106 100644 --- a/docs/extensions/effect.mdx +++ b/docs/extensions/effect.mdx @@ -7,56 +7,95 @@ keywords: effect, atom effect, side effect, side-effect, sideeffect [jotai-effect](https://github.com/jotaijs/jotai-effect) is a utility package for reactive side effects. +These are utilities for declaring side effects and synchronizing atoms in Jotai. They are useful for observing and reacting to atom state changes. + ## install ``` npm install jotai-effect ``` -## atomEffect +## observe + +`observe` mounts an `effect` for watching state changes on the specified Jotai `store`. `observe` is useful for running global side effects or logic at the store level. -`atomEffect` is a utility function for declaring side effects and synchronizing atoms in Jotai. It is useful for observing and reacting to state changes. +If you don't have access to the store object and are not using the default store, you should use `atomEffect` or `withAtomEffect` instead. -### Parameters +### Signature ```ts type CleanupFn = () => void -type EffectFn = ( +type Effect = ( get: Getter & { peek: Getter }, set: Setter & { recurse: Setter }, ) => CleanupFn | void -declare function atomEffect(effectFn: EffectFn): Atom +type Unobserve = () => Reobserve +type Reobserve = () => Unobserve + +function observe(effect: Effect, store?: Store): Unobserve ``` -**effectFn** (required): A function for listening to state updates with `get` and writing state updates with `set`. The `effectFn` is useful for creating side effects that interact with other Jotai atoms. You can cleanup these side effects by returning a cleanup function. +**effect** (required): A function for listening to state updates with `get` and writing state updates with `set`. The `effect` is useful for creating side effects that interact with other Jotai atoms. You can cleanup these side effects by returning a cleanup function. -### Usage +**store** (optional): A Jotai store to mount the effect on. Defaults to the global store if not provided. -Subscribe to Atom Changes +**returns**: An `unobserve` function that, when called, removes the effect from the store and cleans up any internal references. `unobserve` returns a `reobserve` function that can be used to reattach the effect to the store. + +### Usage ```js -import { atomEffect } from 'jotai-effect' +import { observe } from 'jotai-effect' -const loggingEffect = atomEffect((get, set) => { - // runs on mount or whenever someAtom changes - const value = get(someAtom) - loggingService.setValue(value) +// Mount the effect using the default store +const unobserve = observe((get, set) => { + set(logAtom, 'someAtom changed:', get(someAtom)) }) +... +// Clean it up later +const reobserve = unobserve() + +// Reattach the effect to the store +const unobserveAgain = reobserve() ``` -Setup and Teardown Side Effects +This allows you to run Jotai state-dependent logic outside the typical React lifecycle, which can be convenient for application-wide or one-off effects. + +## atomEffect + +`atomEffect` is an atom creator for declaring side effects and synchronizing atoms in Jotai. It is useful for observing and reacting to state changes when the atomEffect is mounted. + +### Signature + +```ts +function atomEffect(effect: Effect): Atom +``` + +**effect** (required): A function for listening to state updates with `get` and writing state updates with `set`. + +### Usage + +Subscribe to Atom Changes ```js import { atomEffect } from 'jotai-effect' -const subscriptionEffect = atomEffect((get, set) => { - const unsubscribe = subscribe((value) => { - set(valueAtom, value) - }) - return unsubscribe +const logEffect = atomEffect((get, set) => { + // runs on mount or whenever someAtom changes + set(logAtom, get(someAtom)) + + return () => { + // unmount is called when the Component unmounts + set(logAtom, 'unmounting') + } }) + +// mounts to activate the atomEffect when Component mounts +function Component() { + useAtom(logEffect) + // ... +} ``` ### Mounting with Atoms or Hooks @@ -66,20 +105,47 @@ After defining an effect using `atomEffect`, it can be integrated within another ```js const anAtom = atom((get) => { // mounts the atomEffect when anAtom mounts - get(loggingEffect) - // ... + get(logEffect) }) -// mounts the atomEffect when the component mounts +// mounts to activate the atomEffect when MyComponent mounts function MyComponent() { - useAtom(subscriptionEffect) + useAtom(logEffect) // ... } ``` - + + +## withAtomEffect + +`withAtomEffect` binds an effect to a clone of the target atom. This is useful for creating effects that are active when the clone of the target atom is mounted. + +### Signature + +```ts +function withAtomEffect(targetAtom: Atom, effect: Effect): Atom +``` + +**targetAtom** (required): The atom to which the effect is bound. -### The `atomEffect` behavior +**effect** (required): A function for listening to state updates with `get` and writing state updates with `set`. + +**Returns:** An atom that is equivalent to the target atom but having a bound effect. + +### Usage + +```js +import { withAtomEffect } from 'jotai-effect' + +const valuesAtom = withAtomEffect(atom(null), (get, set) => { + // runs when valuesAtom is mounted + set(valuesAtom, get(countAtom)) + return unsubscribe +}) +``` + +## The `Effect` behavior - **Cleanup Function:** The cleanup function is invoked on unmount or before re-evaluation. @@ -154,7 +220,7 @@ function MyComponent() { - **Executes In The Next Microtask:** - `effectFn` runs in the next available microtask, after all Jotai synchronous read evaluations have completed. + `effect` runs in the next available microtask, after all Jotai synchronous read evaluations have completed.
@@ -170,7 +236,7 @@ function MyComponent() { get(logAtom) // [0] set(countAtom, increment) // effect runs in next microtask get(logAtom) // [0] - await Promise.resolve().then() + await Promise.resolve() get(logAtom) // [0, 1] }) store.set(setCountAndReadLog) @@ -186,18 +252,19 @@ function MyComponent() { Example ```js - const enabledAtom = atom(false) - const countAtom = atom(0) - const updateEnabledAndCount = atom(null, (get, set) => { - set(enabledAtom, (value) => !value) - set(countAtom, (value) => value + 1) + const countTensAtom = atom(0) + const countOnesAtom = atom(0) + const updateTensAndOnes = atom(null, (get, set) => { + set(countTensAtom, (value) => value + 1) + set(countOnesAtom, (value) => value + 1) }) const combos = atom([]) const combosEffect = atomEffect((get, set) => { - set(combos, (arr) => [...arr, [get(enabledAtom), get(countAtom)]]) + const value = get(countTensAtom) * 10 + get(countOnesAtom) + set(combos, (arr) => [...arr, value]) }) - store.set(updateEnabledAndCount) - store.get(combos) // [[false, 0], [true, 1]] + store.set(updateTensAndOnes) + store.get(combos) // [00, 11] ```
@@ -244,7 +311,7 @@ function MyComponent() { -### Dependency Management +## Dependency Management Aside from mount events, the effect runs when any of its dependencies change value. @@ -311,13 +378,9 @@ Aside from mount events, the effect runs when any of its dependencies change val atomEffect((get, set) => { // runs once on mount // does not update when `idAtom` changes - const unsubscribe = subscribe((valueAtom) => { - const value = get(valueAtom) - // ... - }) + set(logAtom, get(valueAtom)) return () => { - const id = get(idAtom) - unsubscribe(id) + get(idAtom) } }) ``` @@ -347,39 +410,6 @@ Aside from mount events, the effect runs when any of its dependencies change val -## withAtomEffect - -`withAtomEffect` binds an effect to a clone of the target atom. This is useful for creating effects that are active when the clone of the target atom is mounted. - -### Parameters - -```ts -declare function withAtomEffect( - targetAtom: Atom, - effectFn: EffectFn, -): Atom -``` - -**targetAtom** (required): The atom to which the effect is bound. - -**effectFn** (required): A function for listening to state updates with `get` and writing state updates with `set`. - -**Returns:** An atom that is equivalent to the target atom but having a bound effect. - -### Usage - -```js -import { withAtomEffect } from 'jotai-effect' - -const valuesAtom = withAtomEffect(atom(null), (get, set) => { - // runs when valuesAtom is mounted - const unsubscribe = subscribe((value) => { - set(valuesAtom, value) - }) - return unsubscribe -}) -``` - ## Comparison with useEffect ### Component Side Effects