Skip to content

Commit a50d1ff

Browse files
committed
Improve createEffect and createRenderEffect reference
1 parent eef2625 commit a50d1ff

File tree

3 files changed

+260
-118
lines changed

3 files changed

+260
-118
lines changed

src/routes/concepts/effects.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ createEffect(() => {
2424
In this example, an effect is created that logs the current value of `count` to the console.
2525
When the value of `count` changes, the effect is triggered, causing it to run again and log the new value of `count`.
2626

27+
:::note
28+
Effects are primarily intended for handling side effects that do not write to the reactive system.
29+
It's best to avoid setting signals within effects, as this can lead to additional rendering or even infinite loops if not managed carefully.
30+
Instead, it is recommended to use [createMemo](/reference/basic-reactivity/create-memo) to compute new values that rely on other reactive values.
31+
:::
32+
2733
## Managing dependencies
2834

2935
Effects can be set to observe any number of dependencies.

src/routes/reference/basic-reactivity/create-effect.mdx

Lines changed: 121 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2,103 +2,147 @@
22
title: createEffect
33
---
44

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

8-
function createEffect<T>(fn: (v: T) => T, value?: T): void
19+
## Import
920

21+
```ts
22+
import { createEffect } from "solid-js";
1023
```
1124

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+
```
1442

15-
For example:
43+
## Parameters
1644

17-
```tsx
18-
const [a, setA] = createSignal(initialValue)
45+
### `fn`
1946

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
2349

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

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

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
3158

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

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

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
5065

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).
7674

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
8076

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

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+
```
86102

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
90104

91105
```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
99142
```
100143

101-
## Arguments
144+
## Related
102145

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

Comments
 (0)