Stores

createStore

Creates a deeply-reactive store backed by a Proxy. Reads track each property accessed; only the parts that change trigger updates.

Store properties hold plain values, not accessors. The proxy already tracks reads per-property — wrapping a value in () => state.foo produces a getter that won't track when called, which looks like a reactivity bug but is just a category error. If you have a signal-shaped piece of state, make it a property of the store ({ foo: 1 }) rather than nesting an accessor inside ({ foo: () => signal() }).

The setter takes a draft-mutating function — mutate the draft in place (canonical). The callback may also return a new value: arrays are replaced by index (length adjusted), objects are shallow-diffed at the top level (keys present in the returned value are written, missing keys deleted). Use the return form for shapes where mutation is awkward — most commonly removing items via filter. The setter does not do keyed reconciliation; for that, use the derived/projection form (or createProjection).

  • Plain form: createStore(initialValue) — wraps a value in a reactive proxy.
  • Derived form: createStore(fn, seed, options?) — a projection store whose contents are computed by fn(draft). fn may be sync, async, or an AsyncIterable; the projection's result reconciles against the existing store by options.key (default "id") for stable identity.

Import

import { createStore } from "solid-js";

Type signature

function createStore<T extends object = {}>(
store: NoFn<T> | Store<NoFn<T>>
): StoreReturn<T>;
function createStore<T extends object = {}>(
fn: (store: T) => void | T | Promise<void | T> | AsyncIterable<void | T>,
store: Partial<T> | Store<NoFn<T>>,
options?: ProjectionOptions
): ProjectionStoreReturn<T>;

Return value

[store: Store<T>, setStore: StoreSetter<T>]


Examples

const [state, setState] = createStore({
user: { name: "Ada", age: 36 },
todos: [] as { id: string; text: string; done: boolean }[],
});
// Canonical: mutate the draft in place.
setState((s) => {
s.user.age = 37;
});
setState((s) => {
s.todos.push({ id: "1", text: "x", done: false });
});
// Return form: reach for it when mutation is awkward.
setState((s) => s.todos.filter((t) => !t.done)); // remove items
setState((s) => ({ ...s, user: { name: "Grace", age: 85 } })); // shallow replace
// Derived store — auto-fetches & reconciles by `id`.
const [users] = createStore(
async () => fetch("/users").then((r) => r.json()),
[] as User[]
);

ProjectionOptions

Options for derived/projected stores created with createStore(fn), createProjection, or createOptimisticStore(fn).

interface ProjectionOptions extends StoreOptions {
key?: string | ((item: NonNullable<any>) => any);
}

key

  • Type: string | ((item: NonNullable&lt;any&gt;) => any)

Key property name or function for reconciliation identity

ProjectionStoreReturn

Tuple returned by the derived createStore(fn, seed, options?) form.

type ProjectionStoreReturn<T> = [
get: Refreshable<Store<T>>,
set: StoreSetter<T>,
];

Refreshable

Brand applied to values that participate in the refresh() re-run protocol. Accessors receive this handle internally; projected stores expose it through their public return type so user-defined hooks that wrap createOptimisticStore / createProjection / projection-form createStore can have their return types inferred without leaking the internal $REFRESH symbol into public type signatures (TS4058).

type Refreshable<T> = T & { readonly [$REFRESH]: any };

StoreOptions

Base options for store primitives.

interface StoreOptions {
name?: string;
}

name

  • Type: string

Debug name (dev mode only)

StoreReturn

Tuple returned by the plain createStore(initialValue) form.

type StoreReturn<T> = [get: Store<T>, set: StoreSetter<T>];

StoreSetter

A store setter. The callback receives a writable draft of the store.

  • Mutate in place (canonical): s.foo = 1, s.list.push(x), s.list.splice(i, 1). This is the default form for most updates.
  • Return a new value: for shapes where mutation is awkward, most commonly removing items (s => s.list.filter(...)). Arrays are replaced by index (length adjusted); objects are shallow-diffed at the top level (keys present in the returned value are written, missing keys deleted).

The setter does not perform keyed reconciliation. If you need surviving items to keep their store identity across full-array replacement, use the projection form — createStore(fn, seed, { key }) or createProjection — whose derive function reconciles its return by options.key.

type StoreSetter<T> = (fn: (state: T) => T | void) => void;
Last updated: 7/4/26, 6:21 PMEdit this pageReport an issue with this page