createMemo
Creates a readonly derived reactive memoized signal.
compute(prev) runs reactively — every reactive read inside it is
tracked, and the returned value becomes the memo's current value.
The memo is cached: it only recomputes when one of its tracked
sources changes.
const value = createMemo<T>(compute, options?: MemoOptions<T>);Import
import { createMemo } from "solid-js";Type signature
function createMemo<T>( compute: ComputeFunction<NoInfer<T>, T>, options: MemoOptions<T> & { loadingValue: T }): SourceAccessor<T>;function createMemo<T>( compute: ComputeFunction<undefined | NoInfer<T>, T>, options?: MemoOptions<T>): SourceAccessor<T>;Parameters
compute
- Type:
ComputeFunction<T | undefined, T>
Receives the previous value and returns the new value; reads inside are tracked. Returning a promise or an AsyncIterable makes the memo async, and readers suspend until the first value arrives.
options
- Type:
MemoOptions<T> - Optional
name, equals, lazy, loadingValue, deferStream, and ssrSource. See the members below.
Return value
A read-only accessor. Call it to read the memoized value; reading tracks the memo in the surrounding scope.
Examples
const [first, setFirst] = createSignal("Ada");const [last, setLast] = createSignal("Lovelace");
const fullName = createMemo(() => `${first()} ${last()}`);
fullName(); // "Ada Lovelace"// Async memo — reads suspend inside <Loading>const user = createMemo(async () => { const res = await fetch(`/users/${id()}`); return res.json();});Hydration: MemoOptions accepts an ssrSource field
("server" | "hybrid" | "client") that controls what initial
value the client uses and whether compute re-runs. See the ssrSource option. transparent: true opts a memo out of
hydration (it consumes no id slot and computes live); a memo created
with no owner, or under a root without an id, has no id to consume
and takes that path on its own.
Caveats
- Reads inside
computeare tracked; reads in the code that calls the accessor are tracked there, not in the memo. - When
computereturns a promise, reading the accessor before it resolves suspends to the nearestLoadingboundary. Later updates hold the previous value instead. - A memo is for values. Put side effects in
createEffect, not incompute.
Common problems
- A value renders once and never updates
- An effect copies one value into another and the copy lags
- Something updates too often
Learn more
Related types
isEqual
function isEqual<T>(a: T, b: T): boolean;MemoOptions
Deep diveMemoOptions members
Options for read-only memos created with createMemo.
Also used in combination with SignalOptions for writable memos
(createSignal(fn) / createOptimistic(fn)).
interface MemoOptions<T> { id?: string; name?: string; transparent?: boolean; _plumbing?: boolean; equals?: false | ((prev: T, next: T) => boolean); unobserved?: () => void; lazy?: boolean; sync?: boolean; loadingValue?: T; deferStream?: boolean; ssrSource?: "server" | "hybrid" | "client";}id — string
Stable identifier for the owner hierarchy
name — string
Debug name (dev mode only)
transparent — boolean
Advanced (integration tier). When true, the memo is invisible to the
hydration id scheme: it inherits its parent's id instead of consuming a
child slot, and during hydration it computes live instead of adopting
the serialized server value. For client-only memos with no
server-rendered counterpart — see EffectOptions.transparent for
the full semantics. No-op outside hydration.
_plumbing — boolean
equals — false | ((prev: T, next: T) => boolean)
Custom equality function, or false to always notify subscribers.
Defaults to reference equality (isEqual). Pass a comparator (e.g.
(a, b) => a.id === b.id) for value-based equality, or false to
notify on every recompute regardless of equality.
unobserved — () => void
Callback invoked when the computed loses all subscribers
lazy — boolean
When true, defers the initial computation until the value is first read, and opts the memo into autodisposal — once it has no remaining subscribers it is torn down and recomputed from scratch on the next read. Use it for compute-on-demand values that should not retain state across idle periods. Non-lazy owned memos live for their owner's lifetime and never autodispose.
sync — boolean
Advanced. When true, asserts the compute function returns synchronous
values only (never PromiseLike / AsyncIterable). Skips the
async-shape probe in recompute for a small fixed-cost win per run.
Intended for compiler emissions and library code that
provably returns sync values. Returning a Promise or async iterable
from a sync: true memo is undefined behavior — the value will be
stored as-is and never awaited.
loadingValue — T
First committed value: a value the memo is born with, shown until the
compute's first real answer lands. While that first answer is in flight
the memo reads as a settled value everywhere — nothing suspends to a
<Loading> boundary, no update is held (first-flight work is
loading-class, like a boundary fallback), and isPending(memo) stays
false: the declared first value answers the question by declaration, so first-load
affordances are driven from the value itself (a null placeholder, a
skeleton: true field, etc.). Once the first answer lands, the loading
value leaves the lineage forever: refetches use normal pending semantics
(stale value shown, isPending true, and the runtime coordinates the update)
— the canonical guard is data.skeleton || isPending(data), whose two
terms cover the two disjoint states.
Typed strictly as T: to use null/undefined as the placeholder,
declare it in the memo's type (e.g. createMemo<User | null>(...)), so
every consumer sees the nullable window honestly. If the placeholder is
shaped data standing in for real data, encode its provenance in the data
(e.g. a skeleton: true field) rather than letting it impersonate truth.
The loading value is also the compute's first prev, so prev-based
memos fold from it.
deferStream — boolean
Defer the SSR stream flush until this primitive's first value is
resolved. Lets late-resolving sources hold the document open
rather than forcing the surrounding <Loading> boundary to render
its fallback into the HTML. Server-only; ignored on the client.
ssrSource — "server" | "hybrid" | "client"
Hydration policy. Decides what initial value the client uses and whether the compute re-runs.
"server"(default): client uses the serialized server value as initial state. Compute does not re-run for the initial value — the serialized result is authoritative. Choose this when the compute is deterministic from server-available inputs."hybrid": client uses the serialized server value first; then, for a compute that returns an async iterable, the client continues the stream from it (the server consumed exactly one yield; the client's first yield duplicates it and is discarded, later yields update the node). For a sync or promise-shaped compute,"hybrid"is identical to"server": the serialized value is adopted and the compute does not re-run until a dependency changes orrefresh(). Choose this for streaming sources the client should keep consuming after hydration."client": skip the server value entirely. Compute is deferred until hydration completes, then runs as if first-mounted. Choose this for client-only state where serialization is meaningless. Two forms decide what the pre-compute window renders: bare (structural) — the source suspends on the server as a final hole and the nearest<Loading>boundary renders its fallback, handing the position to the client; or a declared first value —loadingValueon signal-family sources (loadingValue: undefinedis a valid declaration),seedLoadingValue: trueon store-family sources — which renders provisional data with no boundary involvement.
SignalOptions
Deep diveSignalOptions members
Options for plain signals created with createSignal(value) or createOptimistic(value).
interface SignalOptions<T> { name?: string; equals?: false | ((prev: T, next: T) => boolean); ownedWrite?: boolean; unobserved?: () => void; deferStream?: boolean; ssrSource?: "server" | "hybrid" | "client";}name — string
Debug name (dev mode only)
equals — false | ((prev: T, next: T) => boolean)
Custom equality function, or false to always notify subscribers.
Defaults to reference equality (isEqual). Pass a comparator (e.g.
(a, b) => a.id === b.id) for value-based equality, or false to
notify on every write regardless of equality.
ownedWrite — boolean
Suppress dev-mode warnings when writing inside an owned scope
unobserved — () => void
Callback invoked when the signal loses all subscribers
deferStream — boolean
Defer the SSR stream flush until this primitive's first value is
resolved. Lets late-resolving sources hold the document open
rather than forcing the surrounding <Loading> boundary to render
its fallback into the HTML. Server-only; ignored on the client.
ssrSource — "server" | "hybrid" | "client"
Hydration policy. Decides what initial value the client uses and whether the compute re-runs.
"server"(default): client uses the serialized server value as initial state. Compute does not re-run for the initial value — the serialized result is authoritative. Choose this when the compute is deterministic from server-available inputs."hybrid": client uses the serialized server value first; then, for a compute that returns an async iterable, the client continues the stream from it (the server consumed exactly one yield; the client's first yield duplicates it and is discarded, later yields update the node). For a sync or promise-shaped compute,"hybrid"is identical to"server": the serialized value is adopted and the compute does not re-run until a dependency changes orrefresh(). Choose this for streaming sources the client should keep consuming after hydration."client": skip the server value entirely. Compute is deferred until hydration completes, then runs as if first-mounted. Choose this for client-only state where serialization is meaningless. Two forms decide what the pre-compute window renders: bare (structural) — the source suspends on the server as a final hole and the nearest<Loading>boundary renders its fallback, handing the position to the client; or a declared first value —loadingValueon signal-family sources (loadingValue: undefinedis a valid declaration),seedLoadingValue: trueon store-family sources — which renders provisional data with no boundary involvement.