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 derived values
Section titled “Selecting derived values”By default selection uses a strict-reference check — perfect for state branches, thanks to Immer’s structural sharing. When a selector builds a new object/array each call, pass an equality function so equal-but-new results don’t re-render:
import { useStoreState } from "@nice-code/state/react";import { deepEqual } from "fast-equals";
const activeTodos = useStoreState( store, (s) => s.todos.filter((t) => !t.done), deepEqual,);Component-local stores
Section titled “Component-local stores”useLocalStore creates a store scoped to a component instance, persisted across renders. Pass a deps array to re-create it (with fresh initial state) when 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>Next: Reactions & watch →