Basic reactivity

createSignal

Edit this page

Signals are the most basic reactive primitive. They track a single value (which can be a value of any type) that changes over time.

import { createSignal } from "solid-js"
// Standard form
function createSignal<T>(
initialValue: T,
options?: {
equals?: false | ((prev: T, next: T) => boolean)
name?: string
internal?: boolean
pureWrite?: boolean
}
): [get: () => T, set: (v: T) => T]
// Function form (writable derived signal) — new in 2.0
function createSignal<T>(
fn: () => T,
options?: { ... }
): [get: () => T, set: (v: T) => T]
// available types for return value of createSignal:
import type { Signal, Accessor, Setter } from "solid-js"
type Signal<T> = [get: Accessor<T>, set: Setter<T>]
type Accessor<T> = () => T
type Setter<T> = (v: T | ((prev?: T) => T)) => T

The Signal's value starts out equal to the passed first argument initialValue (or undefined if there are no arguments). The createSignal function returns a pair of functions as a two-element array: a getter (or accessor) and a setter. In typical use, you would destructure this array into a named Signal like so:

const [count, setCount] = createSignal(0)
const [ready, setReady] = createSignal(false)

Calling the getter (e.g., count() or ready()) returns the current value of the Signal.

Crucial to automatic dependency tracking, calling the getter within a tracking scope causes the calling function to depend on this Signal, so that function will rerun if the Signal gets updated.

Calling the setter (e.g., setCount(nextCount) or setReady(nextReady)) sets the Signal's value and updates the Signal (triggering dependents to rerun) if the value actually changed (see details below). The setter takes either the new value for the signal or a function that maps the previous value of the signal to a new value as its only argument. The updated value is also returned by the setter. As an example:

// read signal's current value, and
// depend on signal if in a tracking scope
// (but nonreactive outside of a tracking scope):
const currentCount = count()
// or wrap any computation with a function,
// and this function can be used in a tracking scope:
const doubledCount = () => 2 * count()
// or build a tracking scope and depend on signal:
const countDisplay = <div>{count()}</div>
// write signal by providing a value:
setReady(true)
// write signal by providing a function setter:
const newCount = setCount((prev) => prev + 1)

Options

NameTypeDefaultDescription
equalsfalse | ((prev: T, next: T) => boolean)===A function that determines whether the Signal's value has changed. If the function returns true, the Signal's value will not be updated and dependents will not rerun. If the function returns false, the Signal's value will be updated and dependents will rerun.
namestringA name for the Signal. This is useful for debugging.
internalbooleanfalseIf true, the Signal will not be accessible in the devtools.
pureWritebooleanfalseIf true, suppresses the dev warning when writing to this signal inside a reactive scope. See pureWrite below.

equals

The equals option can be used to customize the equality check used to determine whether the Signal's value has changed. By default, the equality check is a strict equality check (===). If you want to use a different equality check, you can pass a custom function as the equals option. The custom function will be called with the previous and next values of the Signal as arguments. If the function returns true, the Signal's value will not be updated and dependents will not rerun. If the function returns false, the Signal's value will be updated and dependents will rerun.

const [count, setCount] = createSignal(0, {
equals: (prev, next) => prev === next,
})

Here are some examples of this option in use:

// use { equals: false } to allow modifying object in-place;
// normally this wouldn't be seen as an update because the
// object has the same identity before and after change
const [object, setObject] = createSignal({ count: 0 }, { equals: false })
setObject((current) => {
current.count += 1
current.updated = new Date()
return current
})
// use { equals: false } to create a signal that acts as a trigger without storing a value:
const [depend, rerun] = createSignal(undefined, { equals: false })
// now calling depend() in a tracking scope
// makes that scope rerun whenever rerun() gets called
// define equality based on string length:
const [myString, setMyString] = createSignal("string", {
equals: (newVal, oldVal) => newVal.length === oldVal.length,
})
setMyString("string") // considered equal to the last value and won't cause updates
setMyString("stranger") // considered different and will cause updates

name

The name option can be used to give the Signal a name. This is useful for debugging. The name will be displayed in the devtools.

const [count, setCount] = createSignal(0, { name: "count" })

internal

The internal option can be used to hide the Signal from the devtools. This is useful for Signals that are used internally by a component and should not be exposed to the user.

const [count, setCount] = createSignal(0, { internal: true })

pureWrite

In Solid 2.0, writing to a signal inside a reactive scope (effect, memo, component body) warns in dev. If a signal must be written from within scope (e.g., internal state flags), opt in with pureWrite: true:

const [ref, setRef] = createSignal(null, { pureWrite: true })

pureWrite is not a general-purpose escape hatch. Only use it for internal state that needs to be set during reactive scope — not for application state that should be derived instead.


Function form (writable derived signal)

When a function is passed as the first argument instead of a value, createSignal creates a writable derived signal. The function defines the default derived value, but the signal can be overridden via the setter. This replaces many uses of createComputed.

const [count, setCount] = createSignal(0)
// A writable derived signal: defaults to count() * 2, but can be overridden
const [doubled, setDoubled] = createSignal(() => count() * 2)
doubled() // 0 (derived from count)
setCount(5)
doubled() // 10 (derived from count)
setDoubled(99) // override the derived value
doubled() // 99

This pattern is useful when you want a value that is usually derived but can be temporarily overridden (e.g., optimistic UI, user edits that override computed defaults).


Batching

Report an issue with this page