.agents/skills/zustand-patterns/SKILL.md
Zustand 5.x state management with slices, middleware, Immer, useShallow, and persistence patterns for React applications. Use when building state management with Zustand.
npx skillsauth add OmarHosamCodes/lylrv-reimagined zustand-patternsInstall 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.
Modern state management with Zustand 5.x - lightweight, TypeScript-first, no boilerplate.
import { create } from 'zustand';
interface BearState {
bears: number;
increase: (by: number) => void;
reset: () => void;
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
reset: () => set({ bears: 0 }),
}));
import { create, StateCreator } from 'zustand';
// Auth slice
interface AuthSlice {
user: User | null;
login: (user: User) => void;
logout: () => void;
}
const createAuthSlice: StateCreator<AuthSlice & CartSlice, [], [], AuthSlice> = (set) => ({
user: null,
login: (user) => set({ user }),
logout: () => set({ user: null }),
});
// Cart slice
interface CartSlice {
items: CartItem[];
addItem: (item: CartItem) => void;
clearCart: () => void;
}
const createCartSlice: StateCreator<AuthSlice & CartSlice, [], [], CartSlice> = (set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
clearCart: () => set({ items: [] }),
});
// Combined store
const useStore = create<AuthSlice & CartSlice>()((...a) => ({
...createAuthSlice(...a),
...createCartSlice(...a),
}));
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
interface TodoState {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
updateNested: (id: string, subtaskId: string, done: boolean) => void;
}
const useTodoStore = create<TodoState>()(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
state.todos.push({ id: crypto.randomUUID(), text, done: false });
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id);
if (todo) todo.done = !todo.done;
}),
updateNested: (id, subtaskId, done) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id);
const subtask = todo?.subtasks?.find((s) => s.id === subtaskId);
if (subtask) subtask.done = done;
}),
}))
);
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
interface SettingsState {
theme: 'light' | 'dark';
language: string;
setTheme: (theme: 'light' | 'dark') => void;
}
const useSettingsStore = create<SettingsState>()(
persist(
(set) => ({
theme: 'light',
language: 'en',
setTheme: (theme) => set({ theme }),
}),
{
name: 'settings-storage',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({ theme: state.theme }), // Only persist theme
version: 1,
migrate: (persisted, version) => {
if (version === 0) {
// Migration logic
}
return persisted as SettingsState;
},
}
)
);
// ❌ BAD: Re-renders on ANY state change
const { bears, fish } = useBearStore();
// ✅ GOOD: Only re-renders when bears changes
const bears = useBearStore((state) => state.bears);
// ✅ GOOD: Shallow comparison for objects (Zustand 5.x)
import { useShallow } from 'zustand/react/shallow';
const { bears, fish } = useBearStore(
useShallow((state) => ({ bears: state.bears, fish: state.fish }))
);
// ✅ GOOD: Computed/derived state via selector
const totalAnimals = useBearStore((state) => state.bears + state.fish);
// ❌ BAD: Storing computed state
const useStore = create((set) => ({
items: [],
total: 0, // Don't store derived values!
addItem: (item) => set((s) => ({
items: [...s.items, item],
total: s.total + item.price, // Sync issues!
})),
}));
// ✅ GOOD: Compute in selector
const total = useStore((s) => s.items.reduce((sum, i) => sum + i.price, 0));
interface UserState {
user: User | null;
loading: boolean;
error: string | null;
fetchUser: (id: string) => Promise<void>;
}
const useUserStore = create<UserState>()((set) => ({
user: null,
loading: false,
error: null,
fetchUser: async (id) => {
set({ loading: true, error: null });
try {
const user = await api.getUser(id);
set({ user, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
}));
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
const useStore = create<State>()(
devtools(
(set) => ({
// ... state and actions
}),
{ name: 'MyStore', enabled: process.env.NODE_ENV === 'development' }
)
);
// ✅ Create typed store with double-call pattern
const useStore = create<State>()((set, get) => ({ ... }));
// ✅ Use selectors for all state access
const count = useStore((s) => s.count);
// ✅ Use useShallow for multiple values (Zustand 5.x)
const { a, b } = useStore(useShallow((s) => ({ a: s.a, b: s.b })));
// ✅ Middleware order: immer → subscribeWithSelector → devtools → persist
create(persist(devtools(immer((set) => ({ ... })))))
// ❌ Never destructure entire store
const store = useStore(); // Re-renders on ANY change
// ❌ Never store server state (use TanStack Query instead)
const useStore = create((set) => ({ users: [], fetchUsers: async () => ... }));
| Decision | Option A | Option B | Recommendation |
|----------|----------|----------|----------------|
| State structure | Single store | Multiple stores | Slices in single store - easier cross-slice access |
| Nested updates | Spread operator | Immer middleware | Immer for deeply nested state (3+ levels) |
| Persistence | Manual localStorage | persist middleware | persist middleware with partialize |
| Multiple values | Multiple selectors | useShallow | useShallow for 2-5 related values |
| Server state | Zustand | TanStack Query | TanStack Query - Zustand for client-only state |
| DevTools | Always on | Conditional | Conditional - enabled: process.env.NODE_ENV === 'development' |
// ❌ FORBIDDEN: Destructuring entire store
const { count, increment } = useStore(); // Re-renders on ANY state change
// ❌ FORBIDDEN: Storing derived/computed state
const useStore = create((set) => ({
items: [],
total: 0, // Will get out of sync!
}));
// ❌ FORBIDDEN: Storing server state
const useStore = create((set) => ({
users: [], // Use TanStack Query instead
fetchUsers: async () => { ... },
}));
// ❌ FORBIDDEN: Mutating state without Immer
set((state) => {
state.items.push(item); // Breaks reactivity!
return state;
});
// ❌ FORBIDDEN: Using deprecated shallow import
import { shallow } from 'zustand/shallow'; // Use useShallow from zustand/react/shallow
// ✅ Zustand for CLIENT state (UI, preferences, local-only)
const useUIStore = create<UIState>()((set) => ({
sidebarOpen: false,
theme: 'light',
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));
// ✅ TanStack Query for SERVER state (API data)
function Dashboard() {
const sidebarOpen = useUIStore((s) => s.sidebarOpen);
const { data: users } = useQuery({ queryKey: ['users'], queryFn: fetchUsers });
// Zustand: UI state | TanStack Query: server data
}
tanstack-query-advanced - Server state management (use with Zustand for client state)form-state-patterns - Form state (React Hook Form vs Zustand for forms)react-server-components-framework - RSC hydration considerations with ZustandKeywords: zustand, create, store, typescript, state Solves: Setting up type-safe Zustand stores with proper TypeScript inference
Keywords: slices, modular, split, combine, StateCreator Solves: Organizing large stores into maintainable, domain-specific slices
Keywords: immer, persist, devtools, middleware, compose Solves: Combining middleware in correct order for immutability, persistence, and debugging
Keywords: selector, useShallow, re-render, performance, memoization Solves: Preventing unnecessary re-renders with proper selector patterns
Keywords: persist, localStorage, sessionStorage, migrate, version Solves: Persisting state with schema migrations between versions
references/middleware-composition.md - Combining multiple middlewarescripts/store-template.ts - Production-ready store templatechecklists/zustand-checklist.md - Implementation checklisttools
Use when developing WordPress plugins: architecture and hooks, activation/deactivation/uninstall, admin UI and Settings API, data storage, cron/tasks, security (nonces/capabilities/sanitization/escaping), and release packaging.
tools
React Hook Form performance optimization for client-side form validation using useForm, useWatch, useController, and useFieldArray. This skill should be used when building client-side controlled forms with React Hook Form library. This skill does NOT cover React 19 Server Actions, useActionState, or server-side form handling (use react-19 skill for those).
development
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
development
Drizzle ORM best practices for TypeScript projects using PostgreSQL. Use this skill when writing, reviewing, or optimizing Drizzle ORM schemas, queries, relations, migrations, or database configurations targeting Postgres. Apply these guidelines whenever you see drizzle-orm imports, pgTable definitions, drizzle-kit config files, or relational query patterns. Also use when setting up a new project with Drizzle + Postgres, migrating from another ORM, or troubleshooting type errors in Drizzle schemas.