React Adapter
Subscribe components to stores with useStoreState and related hooks. No provider needed.
Import from @nice-code/state/react. There is no provider: you 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 can build a brand-new object or array on every call (a .filter, a .map, an object literal) without causing needless re-renders. The result is checked by reference (===) first. That is 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, an equality function that defaults to deepEqual decides, so a structurally equal value doesn’t re-render:
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));store.watch() compares values the same way, so both ways of subscribing treat a recomputed-but-equal value alike. 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 go back to 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 persists 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>