skills / secondsky / claude-skills / plugins-zustand-state-management-skills-zustand-state-management plugins-zustand-state-management-skills-zustand-state-management 1 Overview Install Files SecuritySummary This source did not publish a separate summary. Review SKILL.md before using the skill.
Zustand State Management
Status : Production Ready ✅
Last Updated : 2026-08-03
Latest Version : [email protected]
Dependencies : React 18+, TypeScript 5+
Quick Start (3 Minutes)
1. Install Zustand
bun add zustand # preferred
# or: npm install zustand
# or: yarn add zustand
Why Zustand?
Minimal API: Only 1 function to learn (create)
No boilerplate: No providers, reducers, or actions
TypeScript-first: Excellent type inference
Fast: Fine-grained subscriptions prevent unnecessary re-renders
Flexible: Middleware for persistence, devtools, and more
2. Create Your First Store (TypeScript)
import { create } from 'zustand'
interface BearStore {
bears: number
increase: (by: number) => void
reset: () => void
}
const useBearStore = create<BearStore>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
reset: () => set({ bears: 0 }),
}))
CRITICAL : Notice the double parentheses create<T>()() - this is required for TypeScript with middleware.
3. Use Store in Components
import { useBearStore } from './store'
function BearCounter() {
const bears = useBearStore((state) => state.bears)
return <h1>{bears} around here...</h1>
}
function Controls() {
const increase = useBearStore((state) => state.increase)
return <button onClick={() => increase(1)}>Add bear</button>
}
Why this works:
Components only re-render when their selected state changes
No Context providers needed
Selector function extracts specific state slice
The 3-Pattern Setup Process
Pattern 1: Basic Store (JavaScript) For simple use cases without TypeScript:
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
Prototyping
Small apps
No TypeScript in project
Pattern 2: TypeScript Store (Recommended) For production apps with type safety:
import { create } from 'zustand'
// Define store interface
interface CounterStore {
count: number
increment: () => void
decrement: () => void
}
// Create typed store
const useCounterStore = create<CounterStore>()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
Separate interface for state + actions
Use create<T>()() syntax (currying for middleware)
Full IDE autocomplete and type checking
Pattern 3: Persistent Store For state that survives page reloads:
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
interface UserPreferences {
theme: 'light' | 'dark' | 'system'
language: string
setTheme: (theme: UserPreferences['theme']) => void
setLanguage: (language: string) => void
}
const usePreferencesStore = create<UserPreferences>()(
persist(
(set) => ({
theme: 'system',
language: 'en',
setTheme: (theme) => set({ theme }),
setLanguage: (language) => set({ language }),
}),
{
name: 'user-preferences', // unique name in localStorage
storage: createJSONStorage(() => localStorage), // optional: defaults to localStorage
},
),
)
State automatically saved to localStorage
Restored on page reload
Works with sessionStorage too
Handles serialization automatically
Critical Rules
Always Do ✅ Use create<T>()() (double parentheses) in TypeScript for middleware compatibility
✅ Define separate interfaces for state and actions
✅ Use selector functions to extract specific state slices
✅ Use set with updater functions for derived state: set((state) => ({ count: state.count + 1 }))
✅ Use unique names for persist middleware storage keys
✅ Handle Next.js hydration with hasHydrated flag pattern
✅ Use shallow for selecting multiple values
✅ Keep actions pure (no side effects except state updates)
Never Do ❌ Use create<T>(...) (single parentheses) in TypeScript - breaks middleware types
❌ Mutate state directly: set((state) => { state.count++; return state }) - use immutable updates
❌ Create new objects in selectors: useStore((state) => ({ a: state.a })) - causes infinite renders
❌ Use same storage name for multiple stores - causes data collisions
❌ Access localStorage during SSR without hydration check
❌ Use Zustand for server state - use TanStack Query instead
❌ Export store instance directly - always export the hook
Known Issues Prevention (5 Issues) Issue Error Quick Fix #1 Hydration mismatch "Text content does not match" Use _hasHydrated flag + onRehydrateStorage #2 TypeScript inference Types break with middleware Use create<T>()() double parentheses #3 Import error "createJSONStorage not exported" Upgrade to [email protected] + #4 Infinite loop Browser freezes Use shallow or separate selectors #5 Slices types StateCreator types fail Explicit StateCreator<Combined, [], [], Slice>
Most Critical - TypeScript double parentheses:
// ❌ WRONG: create<T>((set) => ...)
// ✅ CORRECT: create<T>()((set) => ...)
See : references/known-issues.md for complete solutions with code examples.
Middleware Configuration import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
const useStore = create<MyStore>()(
devtools(
persist(
(set) => ({ /* store definition */ }),
{ name: 'my-storage' },
),
{ name: 'MyStore' },
),
)
Middleware Purpose Import persistlocalStorage/sessionStorage zustand/middlewaredevtoolsRedux DevTools integration zustand/middlewareimmerMutable update syntax zustand/middleware/immer
Order matters : devtools(persist(...)) shows persist actions in DevTools.
See : references/middleware-guide.md for complete middleware documentation.
Common Patterns Pattern Use Case Key Technique Computed values Derived data Compute in selector: state.items.length Async actions API calls set({ isLoading: true }) + try/catchReset store Logout, form clear set(initialState)Selector with params Dynamic access state.todos.find(t => t.id === id)Multiple stores Separation of concerns Create separate create() calls
See : references/common-patterns.md for complete implementations.
Advanced Topics Topic Use Case Key API Vanilla store Non-React, testing createStore() from zustand/vanillaCustom middleware Logging, timestamps Wrap StateCreator Immer Mutable update syntax immer() middlewareSubscriptions Side effects store.subscribe()
See : references/advanced-topics.md for complete implementations.
Bundled Resources Type Files Templates basic-store.ts, typescript-store.ts, persist-store.ts, slices-pattern.ts, devtools-store.ts, nextjs-store.ts, computed-store.ts, async-actions-store.tsReferences middleware-guide.md, typescript-patterns.md, nextjs-hydration.md, migration-guide.md, known-issues.md, common-patterns.md, advanced-topics.md
When to Load References Reference Load When... known-issues.mdDebugging hydration, TypeScript, infinite loop, or slices errors common-patterns.mdImplementing computed values, async actions, reset patterns advanced-topics.mdVanilla stores, custom middleware, Immer, subscriptions middleware-guide.mdConfiguring persist, devtools, or combining middlewares typescript-patterns.mdComplex type inference issues, StateCreator problems nextjs-hydration.mdNext.js SSR/hydration problems migration-guide.mdMigrating from Redux, Context API, or Zustand v4
Quick Troubleshooting Problem Solution Store updates don't trigger re-renders Use selector: useStore(state => state.value) not destructuring TypeScript errors with middleware Use create<T>()() double parentheses Hydration error with persist Implement _hasHydrated flag pattern Actions not showing in DevTools Pass action name: set(newState, undefined, 'actionName') Store resets unexpectedly HMR causes reset in development
Dependencies { "dependencies": { "zustand": "^5.0.14", "react": "^18.0.0+" } }
Compatibility : React 18+, React 19, TypeScript 5+, Next.js 14+, Vite 5+