Reactivity

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 compute are tracked; reads in the code that calls the accessor are tracked there, not in the memo.
  • When compute returns a promise, reading the accessor before it resolves suspends to the nearest Loading boundary. Later updates hold the previous value instead.
  • A memo is for values. Put side effects in createEffect, not in compute.

Common problems


Learn more


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 or refresh(). 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 — loadingValue on signal-family sources (loadingValue: undefined is a valid declaration), seedLoadingValue: true on 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 or refresh(). 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 — loadingValue on signal-family sources (loadingValue: undefined is a valid declaration), seedLoadingValue: true on store-family sources — which renders provisional data with no boundary involvement.
Last updated: 9/27/26, 8:14 AMEdit this pageReport an issue with this page