Skip to content

Commit ff476f4

Browse files
Merge branch 'main' into docs/rendering-modes
2 parents 6738c75 + 263cedd commit ff476f4

File tree

4 files changed

+391
-127
lines changed

4 files changed

+391
-127
lines changed

src/routes/concepts/effects.mdx

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

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

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

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

Lines changed: 134 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -10,109 +10,165 @@ tags:
1010
- reactivity
1111
- lifecycle
1212
- cleanup
13-
version: '1.0'
13+
version: "1.0"
1414
description: >-
1515
Learn how to use createEffect to run side effects when reactive dependencies
1616
change. Perfect for DOM manipulation and external library integration.
1717
---
1818

19-
```tsx
20-
import { createEffect } from "solid-js"
19+
The `createEffect` primitive creates a reactive computation.
20+
It automatically tracks reactive values, such as [signals](/concepts/signals), accessed within the provided function.
21+
This function will re-run whenever any of its dependencies change.
2122

22-
function createEffect<T>(fn: (v: T) => T, value?: T): void
23+
## Execution Timing
2324

24-
```
25+
### Initial Run
2526

26-
Effects are a general way to make arbitrary code ("side effects") run whenever dependencies change, e.g., to modify the DOM manually.
27-
`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.
27+
- The initial run of effects is **scheduled to occur after the current rendering phase completes**.
28+
- It runs after all synchronous code in a component has finished and DOM elements have been created, but **before the browser paints them on the screen**.
29+
- **[Refs](/concepts/refs) are set** before the first run, even though DOM nodes may not yet be attached to the main document tree.
30+
This is relevant when using the [`children`](/reference/component-apis/children) helper.
2831

29-
For example:
32+
### Subsequent Runs
3033

31-
```tsx
32-
const [a, setA] = createSignal(initialValue)
34+
- After the initial run, the effect **re-runs whenever any tracked dependency changes**.
35+
- When multiple dependencies change within the same batch, the effect **runs once per batch**.
36+
- The **order of runs** among multiple effects is **not guaranteed**.
37+
- Effects always run **after** all pure computations (such as [memos](/concepts/derived-values/memos)) within the same update cycle.
3338

34-
// effect that depends on signal `a`
35-
createEffect(() => doSideEffect(a()))
36-
```
39+
### Server-Side Rendering
3740

38-
The effect will run whenever `a` changes value.
41+
- Effects **never run during SSR**.
42+
- Effects also **do not run during the initial client hydration**.
3943

40-
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.
41-
However, we recommend using `onMount` instead, which is a more explicit way to express this.
44+
## Import
4245

43-
The effect callback can return a value, which will be passed as the `prev` argument to the next invocation of the effect.
44-
This is useful for memoizing values that are expensive to compute. For example:
46+
```ts
47+
import { createEffect } from "solid-js";
48+
```
4549

46-
```tsx
47-
const [a, setA] = createSignal(initialValue)
48-
49-
// effect that depends on signal `a`
50-
createEffect((prevSum) => {
51-
// do something with `a` and `prevSum`
52-
const sum = a() + prevSum
53-
if (sum !== prevSum) console.log("sum changed to", sum)
54-
return sum
55-
}, 0)
56-
// ^ the initial value of the effect is 0
50+
## Type
51+
52+
```ts
53+
function createEffect<Next>(
54+
fn: EffectFunction<undefined | NoInfer<Next>, Next>
55+
): void;
56+
function createEffect<Next, Init = Next>(
57+
fn: EffectFunction<Init | Next, Next>,
58+
value: Init,
59+
options?: { name?: string }
60+
): void;
61+
function createEffect<Next, Init>(
62+
fn: EffectFunction<Init | Next, Next>,
63+
value?: Init,
64+
options?: { name?: string }
65+
): void;
5766
```
5867

59-
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.
60-
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.
68+
## Parameters
6169

62-
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)).
63-
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).
70+
### `fn`
6471

65-
```tsx
66-
// assume this code is in a component function, so is part of a rendering phase
67-
const [count, setCount] = createSignal(0)
68-
69-
// this effect prints count at the beginning and when it changes
70-
createEffect(() => console.log("count =", count()))
71-
// effect won't run yet
72-
console.log("hello")
73-
setCount(1) // effect still won't run yet
74-
setCount(2) // effect still won't run yet
75-
76-
queueMicrotask(() => {
77-
// now `count = 2` will print
78-
console.log("microtask")
79-
setCount(3) // immediately prints `count = 3`
80-
console.log("goodbye")
81-
})
82-
83-
// --- overall output: ---
84-
// hello
85-
// count = 2
86-
// microtask
87-
// count = 3
88-
// goodbye
89-
```
72+
- **Type:** `EffectFunction<undefined | NoInfer<Next> | EffectFunction<Init | Next, Next>`
73+
- **Required:** Yes
74+
75+
A function to be executed as the effect.
76+
77+
It receives the value returned from the previous run, or the initial `value` during the first run.
78+
The value returned by `fn` is passed to the next run.
79+
80+
### `value`
81+
82+
- **Type:** `Init`
83+
- **Required:** No
9084

91-
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.
92-
In particular, [refs](/reference/jsx-attributes/ref) will already be set.
93-
Thus you can use an effect to manipulate the DOM manually, call vanilla JS libraries, or other side effects.
85+
The initial value passed to `fn` during its first run.
9486

95-
Note that the first run of the effect still runs before the browser renders the DOM to the screen (similar to React's `useLayoutEffect`).
96-
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`.
97-
Thus you should use all dependencies before the promise.
87+
### `options`
9888

99-
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).
89+
- **Type:** `{ name?: string }`
90+
- **Required:** No
91+
92+
An optional configuration object with the following properties:
93+
94+
#### `name`
95+
96+
- **Type:** `string`
97+
- **Required:** No
98+
99+
A name for the effect, which can be useful for identification in debugging tools like the [Solid Debugger](https://github.com/thetarnav/solid-devtools).
100+
101+
## Return value
102+
103+
`createEffect` does not return a value.
104+
105+
## Examples
106+
107+
### Basic Usage
108+
109+
```tsx
110+
import { createSignal, createEffect } from "solid-js";
111+
112+
function Counter() {
113+
const [count, setCount] = createSignal(0);
114+
115+
// Every time count changes, this effect re-runs.
116+
createEffect(() => {
117+
console.log("Count incremented! New value: ", count());
118+
});
119+
120+
return (
121+
<div>
122+
<p>Count: {count()}</p>
123+
<button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
124+
</div>
125+
);
126+
}
127+
```
100128

101-
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.
102-
Such a cleanup function gets called both in between effect executions and when the effect gets disposed (e.g., the containing component unmounts).
103-
For example:
129+
### Execution Timing
104130

105131
```tsx
106-
// listen to event dynamically given by eventName signal
107-
createEffect(() => {
108-
const event = eventName()
109-
const callback = (e) => console.log(e)
110-
ref.addEventListener(event, callback)
111-
onCleanup(() => ref.removeEventListener(event, callback))
112-
})
132+
import { createSignal, createEffect, createRenderEffect } from "solid-js";
133+
134+
function Counter() {
135+
const [count, setCount] = createSignal(0);
136+
137+
// This is part of the component's synchronous execution.
138+
console.log("Hello from counter");
139+
140+
// This effect is scheduled to run after the initial render is complete.
141+
createEffect(() => {
142+
console.log("Effect:", count());
143+
});
144+
145+
// By contrast, a render effect runs synchronously during the render phase.
146+
createRenderEffect(() => {
147+
console.log("Render effect:", count());
148+
});
149+
150+
// Setting a signal during the render phase re-runs render effects, but not effects, which are
151+
// still scheduled.
152+
setCount(1);
153+
154+
// A microtask is scheduled to run after the current synchronous code (the render phase) finishes.
155+
queueMicrotask(() => {
156+
// Now that rendering is complete, signal updates will trigger effects immediately.
157+
setCount(2);
158+
});
159+
}
160+
161+
// Output:
162+
// Hello from counter
163+
// Render effect: 0
164+
// Render effect: 1
165+
// Effect: 1
166+
// Render effect: 2
167+
// Effect: 2
113168
```
114169

115-
## Arguments
170+
## Related
116171

117-
- `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.
118-
- `value` - The initial value of the effect. This is useful for memoizing values that are expensive to compute.
172+
- [`createRenderEffect`](/reference/secondary-primitives/create-render-effect)
173+
- [`onCleanup`](/reference/lifecycle/on-cleanup)
174+
- [`onMount`](/reference/lifecycle/on-mount)

src/routes/reference/reactive-utilities/create-root.mdx

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,121 @@ tags:
99
- scopes
1010
- disposal
1111
- tracking
12-
version: '1.0'
12+
version: "1.0"
1313
description: >-
1414
Create non-tracked owner scopes in SolidJS for manual memory management.
1515
Essential for nested tracking scopes and preventing auto-disposal.
1616
---
1717

18+
The `createRoot` function creates a new owned context, which requires explicit disposal of computations it owns.
19+
20+
## Import
21+
22+
```ts
23+
import { createRoot } from "solid-js";
24+
```
25+
26+
## Type
27+
28+
```ts
29+
function createRoot<T>(
30+
fn: (dispose: () => void) => T,
31+
detachedOwner?: Owner
32+
): T;
33+
```
34+
35+
## Parameters
36+
37+
### `fn`
38+
39+
- **Type:** `(dispose: () => void) => T`
40+
- **Required:** Yes
41+
42+
The function executes within a newly created owned context.
43+
The computations created within this function are managed by the root and will only be disposed of when the provided `dispose` function is called.
44+
45+
If a function is passed without a `dispose` parameter, an unowned root is created.
46+
In this case, the computations are not managed for disposal, which may lead to memory leaks.
47+
48+
This function itself does not track dependencies and only runs once.
49+
50+
### `detachedOwner`
51+
52+
- **Type:** `Owner`
53+
- **Required:** No
54+
55+
An optional owner that establishes the root's position in the ownership hierarchy.
56+
When provided, the root becomes owned by this owner and inherits its contextual state (such as [contexts](/concepts/context)).
57+
58+
## Return Value
59+
60+
`createRoot` returns the value returned by the `fn` function.
61+
62+
## Examples
63+
64+
### Basic Usage
65+
66+
```ts
67+
import { createSignal, createEffect, createRoot } from "solid-js";
68+
69+
function createCounter(initial = 0) {
70+
const [count, setCount] = createSignal(initial);
71+
72+
createEffect(() => {
73+
console.log(`Count changed, new value: ${count()}`);
74+
});
75+
76+
function increment() {
77+
setCount((c) => c + 1);
78+
}
79+
80+
function reset() {
81+
setCount(initial);
82+
}
83+
84+
return { count, increment, reset };
85+
}
86+
87+
test("createCounter works correctly", () => {
88+
createRoot((dispose) => {
89+
const { count, increment, reset } = createCounter(10);
90+
91+
expect(count()).toBe(10);
92+
increment();
93+
expect(count()).toBe(11);
94+
reset();
95+
expect(count()).toBe(10);
96+
97+
dispose();
98+
});
99+
});
100+
```
101+
102+
### Returning Values
103+
18104
```ts
19-
import { createRoot } from "solid-js"
105+
import { createRoot, createSignal, onCleanup } from "solid-js";
106+
107+
const counter = createRoot((dispose) => {
108+
const [count, setCount] = createSignal(0);
109+
110+
onCleanup(() => {
111+
console.log("Dispose was called!");
112+
});
20113

21-
function createRoot<T>(fn: (dispose: () => void) => T): T
114+
return {
115+
value: count,
116+
increment: () => setCount((c) => c + 1),
117+
dispose,
118+
};
119+
});
22120

121+
console.log(counter.value()); // 0
122+
counter.increment();
123+
console.log(counter.value()); // 1
124+
counter.dispose(); // Logs "Dispose was called!"
23125
```
24126

25-
Creates a new non-tracked owner scope that doesn't auto-dispose.
26-
This is useful for nested tracking scopes that you do not wish to release when the parent re-evaluates.
127+
## Related
27128

28-
All Solid code should be wrapped in one of these top level as they ensure that all memory/computations are freed up.
29-
Normally you do not need to worry about this as createRoot is embedded into all render entry functions.
129+
- [`render`](/reference/rendering/render)

0 commit comments

Comments
 (0)