dist/plugins/web-state-zustand/skills/web-state-zustand/SKILL.md
Zustand stores, client state patterns. Use when deciding between Zustand vs useState, managing global state, or avoiding Context misuse.
npx skillsauth add agents-inc/skills web-state-zustandInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Local UI state? useState. Shared UI (2+ components)? Zustand. Server data? Use your data fetching solution. URL-appropriate filters? searchParams. NEVER use Context for state management. Zustand v5: use
useShallowfromzustand/react/shallow(not the old equality-fn second arg), selectors must return stable references, andpersistno longer stores initial state during creation.
Detailed Resources:
<critical_requirements>
(You MUST use a data fetching solution for ALL server/API data - NEVER useState, Zustand, or Context)
(You MUST use Zustand for ALL shared UI state (2+ components) - NOT Context or prop drilling)
(You MUST use useState ONLY for truly component-local state - NOT for anything shared)
(You MUST use atomic selectors or useShallow from zustand/react/shallow - NEVER destructure the entire store)
(You MUST ensure selectors return stable references - inline object/function creation causes infinite loops in v5)
</critical_requirements>
Auto-detection: Zustand, zustand, create from zustand, useShallow, zustand/middleware, zustand store, client state, shared UI state, Context misuse, prop drilling, global state
When to use:
Key patterns covered:
When NOT to use:
Zustand is a minimal, hook-based state manager. The key principle: use the right tool for the right job. Server data belongs in a dedicated data fetching layer with caching and synchronization. Local UI state stays in useState. Shared UI state lives in Zustand for performance. URL state makes filters shareable. Context is ONLY for dependency injection, never state management.
Store design principles (from TkDodo and official docs):
The most critical decision: where does this state belong?
Is it server data (from API)?
├─ YES → Data fetching solution (not this skill's scope)
└─ NO → Is it URL-appropriate (filters, search)?
├─ YES → URL params (searchParams)
└─ NO → Is it needed in 2+ components?
├─ YES → Zustand
└─ NO → Is it truly component-local?
├─ YES → useState
└─ NO → Is it a singleton/dependency?
└─ YES → Context (ONLY for DI, not state)
For full examples, see examples/core.md.
Use ONLY when state is truly component-local and never shared.
For good/bad comparisons, see examples/core.md.
Use as soon as state is needed in 2+ components across the tree.
// stores/ui-store.ts
import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";
const UI_STORAGE_KEY = "ui-storage";
interface UIState {
sidebarOpen: boolean;
theme: "light" | "dark";
toggleSidebar: () => void;
setTheme: (theme: "light" | "dark") => void;
}
export const useUIStore = create<UIState>()(
devtools(
persist(
(set) => ({
sidebarOpen: true,
theme: "light",
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
setTheme: (theme) => set({ theme }),
}),
{ name: UI_STORAGE_KEY, partialize: (s) => ({ theme: s.theme }) },
),
),
);
Key points: devtools for debugging, persist only what survives sessions (preferences, not transient UI), partialize to exclude ephemeral state.
For selectors, useShallow, and v5 stability patterns, see examples/core.md.
Context is NOT a state management solution. It's for dependency injection and singletons ONLY.
ONLY use Context for:
NEVER use Context for:
For why Context fails for state and acceptable DI usage, see examples/core.md.
Use URL params (searchParams) for state that should be shareable, bookmarkable, or navigable.
For implementation examples, see examples/core.md.
</patterns><decision_framework>
| Use Case | Solution | Why | | ------------------------------- | ---------------------- | -------------------------------------------- | | Server/API data | Data fetching solution | Caching, synchronization, loading states | | Shareable filters | URL params | Bookmarkable, browser navigation | | Shared UI state (2+ components) | Zustand | Fast, selective re-renders, no prop drilling | | Local UI state (1 component) | useState | Simple, component-local | | Framework providers / DI | Context | Singletons that never change | | ANY state management | NEVER Context | Causes full re-renders on any change |
</decision_framework>
<red_flags>
High Priority Issues:
const { x, y } = useStore() - subscribes to all changes, defeats selective re-renderingMedium Priority Issues:
Gotchas & Edge Cases:
useShallow from zustand/react/shallow or atomic selectorsshallow second argument to create() is removed - use useShallow hook wrapper or createWithEqualityFn from zustand/traditionaluseStore.setState()use-sync-external-store is a peer dependency only when using zustand/traditional</red_flags>
<critical_reminders>
(You MUST use a data fetching solution for ALL server/API data - NEVER useState, Zustand, or Context)
(You MUST use Zustand for ALL shared UI state (2+ components) - NOT Context or prop drilling)
(You MUST use useState ONLY for truly component-local state - NOT for anything shared)
(You MUST use atomic selectors or useShallow from zustand/react/shallow - NEVER destructure the entire store)
(You MUST ensure selectors return stable references - inline object/function creation causes infinite loops in v5)
Failure to follow these rules will cause stale data issues, performance problems, and infinite render loops.
</critical_reminders>
development
Material Design component library for Vue 3
development
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
tools
Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment
development
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety