Skip to content

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.

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);

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,
);

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);
// ...
}

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 →