|
2 | 2 | title: createEffect |
3 | 3 | --- |
4 | 4 |
|
5 | | -```tsx |
6 | | -import { createEffect } from "solid-js" |
| 5 | +The `createEffect` primitive creates a reactive computation. |
| 6 | +It automatically tracks reactive values, such as signals, that are accessed within the provided function. |
| 7 | +This function re-runs whenever any of its dependencies change. |
| 8 | + |
| 9 | +## Execution Timing |
| 10 | + |
| 11 | +- The initial run of an effect is scheduled to run after the current rendering phase completes. |
| 12 | + This means it runs after all synchronous code in a component has been executed, the JSX has been evaluated, and the initial DOM elements have been created and mounted, but before the browser paints them to the screen. |
| 13 | + As a result, [refs](/concepts/refs) are set before an effect runs for the first time. |
| 14 | +- If multiple dependencies are updated within the same batch, the effect only runs once. |
| 15 | +- Effects always run after any pure computations (like [memos](/concepts/derived-values/memos)) in the same update cycle. |
| 16 | +- The order in which effects run is not guaranteed. |
| 17 | +- Effects are not run during Server-Side Rendering (SSR) or the initial client hydration. |
7 | 18 |
|
8 | | -function createEffect<T>(fn: (v: T) => T, value?: T): void |
| 19 | +## Import |
9 | 20 |
|
| 21 | +```ts |
| 22 | +import { createEffect } from "solid-js"; |
10 | 23 | ``` |
11 | 24 |
|
12 | | -Effects are a general way to make arbitrary code ("side effects") run whenever dependencies change, e.g., to modify the DOM manually. |
13 | | -`createEffect` creates a new computation that runs the given function in a tracking scope, thus automatically tracking its dependencies, and automatically reruns the function whenever the dependencies update. |
| 25 | +## Type |
| 26 | + |
| 27 | +```ts |
| 28 | +function createEffect<Next>( |
| 29 | + fn: EffectFunction<undefined | NoInfer<Next>, Next> |
| 30 | +): void; |
| 31 | +function createEffect<Next, Init = Next>( |
| 32 | + fn: EffectFunction<Init | Next, Next>, |
| 33 | + value: Init, |
| 34 | + options?: { name?: string } |
| 35 | +): void; |
| 36 | +function createEffect<Next, Init>( |
| 37 | + fn: EffectFunction<Init | Next, Next>, |
| 38 | + value?: Init, |
| 39 | + options?: { name?: string } |
| 40 | +): void; |
| 41 | +``` |
14 | 42 |
|
15 | | -For example: |
| 43 | +## Parameters |
16 | 44 |
|
17 | | -```tsx |
18 | | -const [a, setA] = createSignal(initialValue) |
| 45 | +### `fn` |
19 | 46 |
|
20 | | -// effect that depends on signal `a` |
21 | | -createEffect(() => doSideEffect(a())) |
22 | | -``` |
| 47 | +- **Type:** `EffectFunction<undefined | NoInfer<Next> | EffectFunction<Init | Next, Next>` |
| 48 | +- **Required:** Yes |
23 | 49 |
|
24 | | -The effect will run whenever `a` changes value. |
| 50 | +The function to run. |
| 51 | +It receives the value returned from the previous run, or the initial `value` on the first run. |
| 52 | +The value it returns is passed to the next run. |
25 | 53 |
|
26 | | -The effect will also run once, immediately after it is created, to initialize the DOM to the correct state. This is called the "mounting" phase. |
27 | | -However, we recommend using `onMount` instead, which is a more explicit way to express this. |
| 54 | +### `value` |
28 | 55 |
|
29 | | -The effect callback can return a value, which will be passed as the `prev` argument to the next invocation of the effect. |
30 | | -This is useful for memoizing values that are expensive to compute. For example: |
| 56 | +- **Type:** `Init` |
| 57 | +- **Required:** No |
31 | 58 |
|
32 | | -```tsx |
33 | | -const [a, setA] = createSignal(initialValue) |
34 | | - |
35 | | -// effect that depends on signal `a` |
36 | | -createEffect((prevSum) => { |
37 | | - // do something with `a` and `prevSum` |
38 | | - const sum = a() + prevSum |
39 | | - if (sum !== prevSum) console.log("sum changed to", sum) |
40 | | - return sum |
41 | | -}, 0) |
42 | | -// ^ the initial value of the effect is 0 |
43 | | -``` |
| 59 | +The initial value passed to `fn` on its first run. |
44 | 60 |
|
45 | | -Effects are meant primarily for side effects that read but don't write to the reactive system: it's best to avoid setting signals in effects, which without care can cause additional rendering or even infinite effect loops. Instead, prefer using [createMemo](/reference/basic-reactivity/create-memo) to compute new values that depend on other reactive values, so the reactive system knows what depends on what, and can optimize accordingly. |
46 | | -If you do end up setting a signal within an effect, computations subscribed to that signal will be executed only once the effect completes; see [`batch`](/reference/reactive-utilities/batch) for more detail. |
| 61 | +### `options` |
47 | 62 |
|
48 | | -The first execution of the effect function is not immediate; it's scheduled to run after the current rendering phase (e.g., after calling the function passed to [render](/reference/rendering/render), [createRoot](/reference/reactive-utilities/create-root), or [runWithOwner](/reference/reactive-utilities/run-with-owner)). |
49 | | -If you want to wait for the first execution to occur, use [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) (which runs before the browser renders the DOM) or `await Promise.resolve()` or `setTimeout(..., 0)` (which runs after browser rendering). |
| 63 | +- **Type:** `{ name?: string }` |
| 64 | +- **Required:** No |
50 | 65 |
|
51 | | -```tsx |
52 | | -// assume this code is in a component function, so is part of a rendering phase |
53 | | -const [count, setCount] = createSignal(0) |
54 | | - |
55 | | -// this effect prints count at the beginning and when it changes |
56 | | -createEffect(() => console.log("count =", count())) |
57 | | -// effect won't run yet |
58 | | -console.log("hello") |
59 | | -setCount(1) // effect still won't run yet |
60 | | -setCount(2) // effect still won't run yet |
61 | | - |
62 | | -queueMicrotask(() => { |
63 | | - // now `count = 2` will print |
64 | | - console.log("microtask") |
65 | | - setCount(3) // immediately prints `count = 3` |
66 | | - console.log("goodbye") |
67 | | -}) |
68 | | - |
69 | | -// --- overall output: --- |
70 | | -// hello |
71 | | -// count = 2 |
72 | | -// microtask |
73 | | -// count = 3 |
74 | | -// goodbye |
75 | | -``` |
| 66 | +An optional configuration object with the following properties: |
| 67 | + |
| 68 | +#### `name` |
| 69 | + |
| 70 | +- **Type:** `string` |
| 71 | +- **Required:** No |
| 72 | + |
| 73 | +A name for the effect, used for identification in debugging tools like [Solid Debugger](https://github.com/thetarnav/solid-devtools). |
76 | 74 |
|
77 | | -This delay in first execution is useful because it means an effect defined in a component scope runs after the JSX returned by the component gets added to the DOM. |
78 | | -In particular, [refs](/reference/jsx-attributes/ref) will already be set. |
79 | | -Thus you can use an effect to manipulate the DOM manually, call vanilla JS libraries, or other side effects. |
| 75 | +## Return value |
80 | 76 |
|
81 | | -Note that the first run of the effect still runs before the browser renders the DOM to the screen (similar to React's `useLayoutEffect`). |
82 | | -If you need to wait until after rendering (e.g., to measure the rendering), you can use `await Promise.resolve()` (or `Promise.resolve().then(...)`), but note that subsequent use of reactive state (such as signals) will not trigger the effect to rerun, as tracking is not possible after an async function uses `await`. |
83 | | -Thus you should use all dependencies before the promise. |
| 77 | +`createEffect` does not return a value. |
84 | 78 |
|
85 | | -If you'd rather an effect run immediately even for its first run, use [createRenderEffect](/reference/secondary-primitives/create-render-effect) or [createComputed](/reference/secondary-primitives/create-computed). |
| 79 | +## Examples |
| 80 | + |
| 81 | +### Basic Usage |
| 82 | + |
| 83 | +```tsx |
| 84 | +import { createSignal, createEffect } from "solid-js"; |
| 85 | + |
| 86 | +function Counter() { |
| 87 | + const [count, setCount] = createSignal(0); |
| 88 | + |
| 89 | + // Every time count changes this effect re-runs. |
| 90 | + createEffect(() => { |
| 91 | + console.log("Count incremented! New value: ", count()); |
| 92 | + }); |
| 93 | + |
| 94 | + return ( |
| 95 | + <div> |
| 96 | + <p>Count: {count()}</p> |
| 97 | + <button onClick={() => setCount((prev) => prev + 1)}>Increment</button> |
| 98 | + </div> |
| 99 | + ); |
| 100 | +} |
| 101 | +``` |
86 | 102 |
|
87 | | -You can clean up your side effects in between executions of the effect function by calling [onCleanup](/reference/lifecycle/on-cleanup) inside the effect function. |
88 | | -Such a cleanup function gets called both in between effect executions and when the effect gets disposed (e.g., the containing component unmounts). |
89 | | -For example: |
| 103 | +### Execution Timing |
90 | 104 |
|
91 | 105 | ```tsx |
92 | | -// listen to event dynamically given by eventName signal |
93 | | -createEffect(() => { |
94 | | - const event = eventName() |
95 | | - const callback = (e) => console.log(e) |
96 | | - ref.addEventListener(event, callback) |
97 | | - onCleanup(() => ref.removeEventListener(event, callback)) |
98 | | -}) |
| 106 | +import { createSignal, createEffect, createRenderEffect } from "solid-js"; |
| 107 | + |
| 108 | +function Counter() { |
| 109 | + const [count, setCount] = createSignal(0); |
| 110 | + |
| 111 | + // This is part of the component's synchronous execution. |
| 112 | + console.log("Hello from counter"); |
| 113 | + |
| 114 | + // This effect is scheduled to run after the initial render is complete. |
| 115 | + createEffect(() => { |
| 116 | + console.log("Effect:", count()); |
| 117 | + }); |
| 118 | + |
| 119 | + // By contrast, a render effect runs synchronously during the render phase. |
| 120 | + createRenderEffect(() => { |
| 121 | + console.log("Render effect:", count()); |
| 122 | + }); |
| 123 | + |
| 124 | + // Setting a signal during the render phase re-runs render effects, but not effects, which are |
| 125 | + // still scheduled. |
| 126 | + setCount(1); |
| 127 | + |
| 128 | + // A microtask is scheduled to run after the current synchronous code (the render phase) finishes. |
| 129 | + queueMicrotask(() => { |
| 130 | + // Now that rendering is complete, signal updates will trigger effects immediately. |
| 131 | + setCount(2); |
| 132 | + }); |
| 133 | +} |
| 134 | + |
| 135 | +// Output: |
| 136 | +// Hello from counter |
| 137 | +// Render effect: 0 |
| 138 | +// Render effect: 1 |
| 139 | +// Effect: 1 |
| 140 | +// Render effect: 2 |
| 141 | +// Effect: 2 |
99 | 142 | ``` |
100 | 143 |
|
101 | | -## Arguments |
| 144 | +## Related |
102 | 145 |
|
103 | | -- `fn` - The function to run in a tracking scope. It can return a value, which will be passed as the `prev` argument to the next invocation of the effect. |
104 | | -- `value` - The initial value of the effect. This is useful for memoizing values that are expensive to compute. |
| 146 | +- [`createRenderEffect`](/reference/secondary-primitives/create-render-effect) |
| 147 | +- [`onCleanup`](/reference/lifecycle/on-cleanup) |
| 148 | +- [`onMount`](/reference/lifecycle/on-mount) |
0 commit comments