Skip to content

Commit

Permalink
doc: update jotai-effect docs
Browse files Browse the repository at this point in the history
  • Loading branch information
dmaskasky committed Jan 25, 2025
1 parent 2406824 commit 44b983d
Showing 1 changed file with 105 additions and 75 deletions.
180 changes: 105 additions & 75 deletions docs/extensions/effect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>
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<void>
```

**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
Expand All @@ -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)
// ...
}
```

<Stackblitz id="vitejs-vite-fj5zjp" file="src%2FApp.tsx" />
<CodeSandbox id="tg9xsf" />

## 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<T>(targetAtom: Atom<T>, effect: Effect): Atom<T>
```

**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.
Expand Down Expand Up @@ -154,7 +220,7 @@ function MyComponent() {
</details>

- **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.

<!-- prettier-ignore -->
<details style="cursor: pointer; user-select: none;">
Expand All @@ -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)
Expand All @@ -186,18 +252,19 @@ function MyComponent() {
<summary>Example</summary>

```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]
```

</details>
Expand Down Expand Up @@ -244,7 +311,7 @@ function MyComponent() {

</details>

### Dependency Management
## Dependency Management

Aside from mount events, the effect runs when any of its dependencies change value.

Expand Down Expand Up @@ -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)
}
})
```
Expand Down Expand Up @@ -347,39 +410,6 @@ Aside from mount events, the effect runs when any of its dependencies change val

</details>

## 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<T>(
targetAtom: Atom<T>,
effectFn: EffectFn,
): Atom<T>
```

**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
Expand Down

0 comments on commit 44b983d

Please sign in to comment.