Imported from danielangelo1/ai-agent-template (
.claude/skills/client-state-zustand/SKILL.md). Install upstream withnpx skills add danielangelo1/ai-agent-template --skill client-state-zustand. Copyright stays with the author.
- Zustand is for state that (a) is read by components that don't share a parent, or
(b) changes frequently enough that Context would cause wide re-renders — e.g. a dialog's
open/closed flag read by both the trigger and the dialog itself (
src/lib/store.ts,useUiStore). - Plain
React.useState/Context is still correct for state genuinely local to one component subtree, or for low-frequency global state (theme, locale) where Context's broader re-render is not a real cost — do not reach for Zustand by default. - One store per concern, not one giant app-wide store —
useUiStorehere only owns UI-only state (dialog visibility), never server data (that is TanStack Query's job, never duplicated into Zustand). - Select the narrowest slice a component needs (
useUiStore((s) => s.isSubscribeDialogOpen)), never the whole store object — a whole-store selector re-renders on every field change. - Actions live on the store itself (
openSubscribeDialog,closeSubscribeDialog), not as ad-hocsetStatecalls scattered across components — keeps the state's invariants in one place.