React
useStore — a single hook that subscribes a component to a createStore or derive instance.
@zap-studio/store-react is a separate package with one hook: useStore(store, selector?). It subscribes a component to a createStore or derive instance, and re-renders only when the subscribed value changes.
Installation
npm install @zap-studio/store-react @zap-studio/store
Import
import { useStore } from "@zap-studio/store-react";
Basic Usage
import { createStore } from "@zap-studio/store";
import { useStore } from "@zap-studio/store-react";
const counter = createStore({ count: 0 }, (set) => ({
increment: () => set((s) => ({ count: s.count + 1 })),
}));
function Counter() {
const count = useStore(counter, (s) => s.count);
return <button onClick={() => counter.get().increment()}>{count}</button>;
}
Without a Selector
useStore(store) re-renders on every change to store. Both createStore and derive results are cached — get() returns the same reference until the state actually changes — so this is always safe, it just re-renders on more changes than a selector would:
const isEven = derive([counter], (s) => s.count % 2 === 0);
function Parity() {
const even = useStore(isEven);
return even ? "even" : "odd";
}
With a Selector
Pass a selector to narrow what the component reads. The component only re-renders when the selector’s result changes, compared with Object.is:
const count = useStore(counter, (s) => s.count);
Reading actions this way is also fine — actions are bound once at creation, so s.increment never changes:
const increment = useStore(counter, (s) => s.increment);
Prefer derive Over Manual Equality Checks
Zustand’s React binding needs a selector plus a manual shallow comparison to avoid re-rendering when a selector returns a new object or array every time. store solves this at the source instead: build the exact value you need with derive, which caches its result and only changes reference when the value itself changes.
// Prefer this — derive() caches the computed object:
const summary = derive([counter], (s) => ({ count: s.count, isEven: s.count % 2 === 0 }));
const { count, isEven } = useStore(summary);
// Over this — a new object every render, would need a manual shallow-equality check:
const { count, isEven } = useStore(counter, (s) => ({
count: s.count,
isEven: s.count % 2 === 0,
}));
Concurrent Rendering
useStore is built on React’s own useSyncExternalStore, so it is safe under concurrent rendering — no custom subscription-in-useEffect code, and no risk of tearing between renders.
See Also
- Getting Started — creating a store to use with this hook
derive— auto-tracked, cached derived values