Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Generated phase 3
  • Loading branch information
davedbase committed Mar 4, 2026
commit 127cf4dca4a6b4f5af7cabb224161f787dc7970c
137 changes: 63 additions & 74 deletions src/routes/reference/basic-reactivity/create-effect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,109 +10,98 @@ tags:
- reactivity
- lifecycle
- cleanup
version: '1.0'
version: '2.0'
description: >-
Learn how to use createEffect to run side effects when reactive dependencies
change. Perfect for DOM manipulation and external library integration.
Learn how to use createEffect's split compute/apply model to run side effects
when reactive dependencies change. Perfect for DOM manipulation and external
library integration.
---

:::caution[Solid 2.0 — Split effect model]
In Solid 2.0, `createEffect` uses a **split model** with two functions: a **compute** function (tracking phase) and an **apply** function (side-effect phase). The single-callback form is no longer supported — use `createTrackedEffect` if you need it.
:::

```tsx
import { createEffect } from "solid-js"

function createEffect<T>(fn: (v: T) => T, value?: T): void

function createEffect<T>(
compute: () => T,
apply?: (value: T) => (() => void) | void
): void
```

Effects are a general way to make arbitrary code ("side effects") run whenever dependencies change, e.g., to modify the DOM manually.
`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.

For example:
The **compute** function runs in a tracking scope — it reads reactive values and returns a result. The **apply** function receives that result and performs the side effect. The apply function can optionally return a **cleanup** function that runs before the next execution or when the effect is disposed.

```tsx
const [a, setA] = createSignal(initialValue)
const [count, setCount] = createSignal(0)

// effect that depends on signal `a`
createEffect(() => doSideEffect(a()))
createEffect(
() => count(), // compute: track dependencies, return value
(value) => { // apply: perform side effect with the value
console.log("count =", value)
return () => { // optional cleanup
console.log("cleaning up for count", value)
}
}
)
```

The effect will run whenever `a` changes value.
### Why split?

Splitting tracking from side effects means Solid can run all compute phases before any apply phases. This:

- Gives a clear dependency picture before side effects execute
- Enables async, `Loading`, and `Errored` boundaries to work correctly
- Prevents accidental feedback loops from signal writes in tracked scope

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.
However, we recommend using `onMount` instead, which is a more explicit way to express this.
### Compute-only effects

The effect callback can return a value, which will be passed as the `prev` argument to the next invocation of the effect.
This is useful for memoizing values that are expensive to compute. For example:
If you only need tracking (no side effect), you can omit the apply function:

```tsx
const [a, setA] = createSignal(initialValue)

// effect that depends on signal `a`
createEffect((prevSum) => {
// do something with `a` and `prevSum`
const sum = a() + prevSum
if (sum !== prevSum) console.log("sum changed to", sum)
return sum
}, 0)
// ^ the initial value of the effect is 0
createEffect(() => {
// This runs in a tracking scope
// Useful for registering dependencies
trackDependency(someSignal())
})
```

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.
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.
### Cleanup

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)).
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).
The apply function can return a cleanup function. This cleanup runs:
- Before the next execution of the apply function
- When the effect is disposed (e.g., the containing component unmounts)

```tsx
// assume this code is in a component function, so is part of a rendering phase
const [count, setCount] = createSignal(0)

// this effect prints count at the beginning and when it changes
createEffect(() => console.log("count =", count()))
// effect won't run yet
console.log("hello")
setCount(1) // effect still won't run yet
setCount(2) // effect still won't run yet

queueMicrotask(() => {
// now `count = 2` will print
console.log("microtask")
setCount(3) // immediately prints `count = 3`
console.log("goodbye")
})

// --- overall output: ---
// hello
// count = 2
// microtask
// count = 3
// goodbye
createEffect(
() => eventName(),
(event) => {
const callback = (e) => console.log(e)
ref.addEventListener(event, callback)
return () => ref.removeEventListener(event, callback) // cleanup
}
)
```

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.
In particular, [refs](/reference/jsx-attributes/ref) will already be set.
Thus you can use an effect to manipulate the DOM manually, call vanilla JS libraries, or other side effects.
### Execution timing

Note that the first run of the effect still runs before the browser renders the DOM to the screen (similar to React's `useLayoutEffect`).
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`.
Thus you should use all dependencies before the promise.
The first execution of the effect is not immediate; it's scheduled to run after the current rendering phase. If you need to wait for the DOM to be ready, use [`onSettled`](/reference/lifecycle/on-mount).

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).
:::caution[Solid 2.0 — No signal writes in effects]
Writing to signals inside effects warns in dev. Effects are for side effects (DOM updates, logging, external syncing) — not for deriving state. Use [`createMemo`](/reference/basic-reactivity/create-memo) or [derived signals](/concepts/derived-values/derived-signals) instead. If you must write a signal from within a reactive scope, use the `{ pureWrite: true }` option on the signal.
:::

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

```tsx
// listen to event dynamically given by eventName signal
createEffect(() => {
const event = eventName()
const callback = (e) => console.log(e)
ref.addEventListener(event, callback)
onCleanup(() => ref.removeEventListener(event, callback))
})
```
| Name | Type | Description |
| :--- | :--- | :---------- |
| compute | `() => T` | A function that runs in a tracking scope, reads reactive values, and returns a value passed to `apply`. |
| apply | `(value: T) => (() => void) \| void` | An optional function that receives the computed value and performs side effects. Can return a cleanup function. |

## Arguments
## Related

- `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.
- `value` - The initial value of the effect. This is useful for memoizing values that are expensive to compute.
- [`createTrackedEffect`](/reference/secondary-primitives/create-render-effect) — Single-callback form for effects that need the older pattern.
- [`onSettled`](/reference/lifecycle/on-mount) — Runs once after the component's initial render is complete.
- [`onCleanup`](/reference/lifecycle/on-cleanup) — Registers cleanup within a reactive computation.
47 changes: 42 additions & 5 deletions src/routes/reference/basic-reactivity/create-memo.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ tags:
- derived-state
- caching
- reactivity
version: '1.0'
version: '2.0'
description: >-
Use createMemo to efficiently compute and cache derived values. Prevent
expensive recalculations and optimize your Solid.js application's performance.
Use createMemo to efficiently compute and cache derived values. Supports async
Promises for data fetching with automatic suspension.
---

Memos let you efficiently use a derived value in many reactive computations.
Expand Down Expand Up @@ -74,13 +74,50 @@ This is useful for reducing computations, such as:
const sum = createMemo((prev) => input() + prev, 0)
```

The memo function should not change other signals by calling setters (it should be "pure").
The memo function should not change other signals by calling setters (it should be "pure").
This enables Solid to optimize the execution order of memo updates according to their dependency graph, so that all memos can update at most once in response to a dependency change.

:::caution[Solid 2.0 — No signal writes in memos]
Writing to signals inside a memo warns in dev. Memos should be pure computations. If you need to write a signal based on reactive state, use an event handler or `onSettled` instead.
:::

## Async memos

:::note[Solid 2.0 — Async support]
In Solid 2.0, `createMemo` can return a Promise. When an async memo is read before the Promise resolves, it suspends — causing the nearest `<Loading>` boundary to show its fallback. This replaces `createResource` for data fetching.
:::

```tsx
import { createMemo } from "solid-js"

const user = createMemo(() => fetchUser(userId()))

// In JSX, wrap in Loading to handle the pending state
<Loading fallback={<Spinner />}>
<div>{user().name}</div>
</Loading>
```

Async memos work with all reactive features:
- **`isPending(() => memo())`** — check if the memo is revalidating (stale-while-revalidating)
- **`latest(() => memo())`** — read the in-flight value during transitions
- **`<Errored>`** — catch errors thrown by the async computation

```tsx
const users = createMemo(() => fetchUsers())

<Show when={isPending(() => users())}>
<RefreshingIndicator />
</Show>
<Loading fallback={<Spinner />}>
<UserList users={users()} />
</Loading>
```

## Options and arguments

| Name | Type | Description |
| :------ | :------------------------------------------------------ | :------------------------------------------------------------- |
| fn | `(v: T) => T` | The function to memoize. |
| fn | `(v: T) => T` | The function to memoize. May return a Promise for async data. |
| value | `T` | The initial value of the memo. |
| options | `{ equals?: false \| ((prev: T, next: T) => boolean) }` | An optional object with an `equals` function to test equality. |
Loading