Basic Reactivity

createEffect

Edit this page
import { createEffect } from "solid-js"
function createEffect<T>(fn: (v: T) => T, value?: T): 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:

const [a, setA] = createSignal(initialValue)
// effect that depends on signal `a`
createEffect(() => doSideEffect(a()))

The effect will run whenever a changes value.

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.

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:

const [a, setA] = createSignal(initialValue)
// effect that depends on signal `a`
createEffect((prevSum) => {
// do something with `a` and `prevSum`
const sum = a() + b()
if (sum !== prevSum) console.log("sum changed to", sum)
return sum
}, 0)
// ^ the initial value of the effect is 0

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 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 for more detail.

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, createRoot, or runWithOwner). If you want to wait for the first execution to occur, use queueMicrotask (which runs before the browser renders the DOM) or await Promise.resolve() or setTimeout(..., 0) (which runs after browser rendering).

// 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

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 will already be set. Thus you can use an effect to manipulate the DOM manually, call vanilla JS libraries, or other side effects.

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.

If you'd rather an effect run immediately even for its first run, use createRenderEffect or createComputed.

You can clean up your side effects in between executions of the effect function by calling onCleanup 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:

// 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))
})

Arguments

  • 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.
Report an issue with this page