React Adapter
Subscribe components to stores with useStoreState and friends — no provider.
Import from @nice-code/state/react. No provider needed — pass the store directly. react >= 19 is an optional peer dependency.
useStoreState
Section titled “useStoreState”import { useStoreState } from "@nice-code/state/react";
function Counter() { // Select a slice — re-renders only when `count` changes. const count = useStoreState(counterStore, (s) => s.count);
return ( <button onClick={() => counterStore.update((s) => { s.count += 1; })}> {count} </button> );}// No selector → subscribe to the whole state.const state = useStoreState(counterStore);Selecting computed values
Section titled “Selecting computed values”A selector’s result is checked by reference (===) first — the fast path, and exactly right for slices of your state, because Immer reuses the objects that didn’t change. When the reference does differ, the result falls through to an equality function that defaults to deepEqual, so a selector that builds a brand-new object or array every call (a .filter, a .map, an object literal) doesn’t cause a needless re-render when the value is structurally the same:
import { useStoreState } from "@nice-code/state/react";
// Rebuilds a new array each call, but only re-renders when the filtered result// actually changes — the deepEqual default absorbs the new-but-equal case.const activeTodos = useStoreState(store, (s) => s.todos.filter((t) => !t.done));This matches store.watch()’s equality behavior, so both ways of subscribing treat a recomputed-but-equal value the same. The deep comparison only runs on a genuine reference change and is bounded by the size of the selected slice. If a selector returns a very large slice on a hot path and you’d rather skip the traversal, pass a strict comparator to opt back into reference-only checks:
const bigSlice = useStoreState(store, (s) => s.hugeThing, (a, b) => a === b);Stores that live inside one component
Section titled “Stores that live inside one component”useLocalStore makes a store that belongs to a single component instance and sticks around across renders. Pass a deps array to rebuild it (with fresh initial state) whenever those dependencies change.
import { useLocalStore, useStoreState } from "@nice-code/state/react";
function Editor({ docId }: { docId: string }) { const store = useLocalStore(() => ({ draft: "" }), [docId]); const draft = useStoreState(store, (s) => s.draft); // ...}Render-prop binding
Section titled “Render-prop binding”InjectStoreState subscribes inline without writing a hook:
import { InjectStoreState } from "@nice-code/state/react";
<InjectStoreState store={counterStore} on={(s) => s.count}> {(count) => <span>{count}</span>}</InjectStoreState>