Skip to main content
useStore<T>(key: string, defaultValue: T, scope?: string): Store<StoreShape<T>>;
key
string
required
A unique identifier for this store. All components that call useStore with the same key share the same state.
defaultValue
T
required
The initial value for the store. Objects are stored as-is. Primitives are wrapped in { value: T } automatically.
scope
string
An optional namespace for the store key. The resolved key becomes "scope:key". Use this to isolate stores per-instance (e.g. pass a user ID or item ID as the scope).

Returns

A Store object that combines:
  • State fields — All properties from the default value, readable directly (e.g. store.count, store.name).
  • Per-field setters — Auto-generated set<FieldName>() methods for each property (e.g. store.setCount(5)). Each setter accepts a direct value or an updater function (prev) => next.
  • set(value) — Replaces the entire store value. Accepts a direct value or an updater function (prev) => next.
  • Metadatakey (the key passed to useStore), scope (the scope, if provided), and fullKey (the resolved key "scope:key" or "key").

Usage

Basic object store

const body = () => {
    const user = useStore("user", {
        name: "John",
        age: 30,
        email: "john@example.com"
    })

    return VStack([
        Text("Name: " + user.name),
        Text("Age: " + String(user.age)),
        Button("Birthday", () => user.setAge(user.age + 1)),
        Button("Rename", () => user.setName("Jane"))
    ])
}

Primitive values

Primitives are wrapped in { value: T }, so the setter is setValue:
const body = () => {
    const counter = useStore("counter", 0)

    return VStack([
        Text("Count: " + String(counter.value)),
        Button("Increment", () => counter.setValue(counter.value + 1))
    ])
}

Functional updates

All setters accept an updater function for updates based on the previous value:
const body = () => {
    const counter = useStore("counter", { count: 0 })

    return VStack([
        Text("Count: " + String(counter.count)),
        Button("Increment", () => {
            counter.setCount(prev => prev + 1)
        })
    ])
}

Scoped stores

Use the scope parameter to namespace stores per-instance:
const body = (props) => {
    const prefs = useStore("prefs", {
        theme: "light",
        fontSize: 16
    }, props.userId)

    // Full key: "userId123:prefs"
    return VStack([
        Text("Theme: " + prefs.theme),
        Button("Toggle Theme", () => {
            prefs.setTheme(prefs.theme === "light" ? "dark" : "light")
        })
    ])
}

Replacing the entire store

const body = () => {
    const profile = useStore("profile", {
        name: "John",
        bio: "Developer"
    })

    return VStack([
        Button("Reset", () => {
            profile.set({ name: "Anonymous", bio: "" })
        }),
        Button("Uppercase Name", () => {
            profile.set(prev => ({
                ...prev,
                name: prev.name.toUpperCase()
            }))
        })
    ])
}

Shared state across components

Stores are shared by key, so separate components can read and write the same state:
// Component A
const body = () => {
    const cart = useStore("cart", { items: [], total: 0 })
    return Text("Cart total: $" + String(cart.total))
}
// Component B
const body = () => {
    const cart = useStore("cart", { items: [], total: 0 })
    return Button("Add Item", () => {
        cart.setTotal(cart.total + 9.99)
    })
}

Notes

  • Store state is global and persists across re-renders. Any component that calls useStore with the same key (and scope) shares the same state instance.
  • The defaultValue is only used when the store is first created. Subsequent calls with the same key return the existing state regardless of the defaultValue passed.
  • Updates to the store trigger re-renders in all components that read from it.
  • For component-local state that does not need to be shared, use useState instead.
  • useStore supersedes useAppState, which provides a simpler tuple-based API without per-field setters or scoping. Prefer useStore for new components.
  • useStore must be called inside a component body function, not at the top level.