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 byfn(draft).fnmay be sync, async, or anAsyncIterable; the projection's result reconciles against the existing store byoptions.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 itemssetState((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[]);Related types
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<any>) => 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;