Zustand State Management
Last Updated: 2026-01-21 Latest Version: zustand@5.0.10 (released 2026-01-12) Dependencies: React 18-19, TypeScript 5+
Quick Start npm install zustand
TypeScript Store (CRITICAL: use create
import { create } from 'zustand'
interface BearStore { bears: number increase: (by: number) => void }
const useBearStore = create
Use in Components:
const bears = useBearStore((state) => state.bears) // Only re-renders when bears changes const increase = useBearStore((state) => state.increase)
Core Patterns
Basic Store (JavaScript):
const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), }))
TypeScript Store (Recommended):
interface CounterStore { count: number; increment: () => void }
const useStore = create
Persistent Store (survives page reloads):
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create
Critical Rules Always Do
✅ Use create
Never Do
❌ Use create
Known Issues Prevention
This skill prevents 6 documented issues:
Issue #1: Next.js Hydration Mismatch
Error: "Text content does not match server-rendered HTML" or "Hydration failed"
Source:
DEV Community: Persist middleware in Next.js GitHub Discussions #2839
Why It Happens: Persist middleware reads from localStorage on client but not on server, causing state mismatch.
Prevention:
import { create } from 'zustand' import { persist } from 'zustand/middleware'
interface StoreWithHydration { count: number _hasHydrated: boolean setHasHydrated: (hydrated: boolean) => void increase: () => void }
const useStore = create
// In component function MyComponent() { const hasHydrated = useStore((state) => state._hasHydrated)
if (!hasHydrated) { return
// Now safe to render with persisted state
return
Issue #2: TypeScript Double Parentheses Missing
Error: Type inference fails, StateCreator types break with middleware
Source: Official Zustand TypeScript Guide
Why It Happens: The currying syntax create
Prevention:
// ❌ WRONG - Single parentheses
const useStore = create
// ✅ CORRECT - Double parentheses
const useStore = create
Rule: Always use create
Issue #3: Persist Middleware Import Error
Error: "Attempted import error: 'createJSONStorage' is not exported from 'zustand/middleware'"
Source: GitHub Discussion #2839
Why It Happens: Wrong import path or version mismatch between zustand and build tools.
Prevention:
// ✅ CORRECT imports for v5 import { create } from 'zustand' import { persist, createJSONStorage } from 'zustand/middleware'
// Verify versions // zustand@5.0.9 includes createJSONStorage // zustand@4.x uses different API
// Check your package.json // "zustand": "^5.0.9"
Issue #4: Infinite Render Loop
Error: Component re-renders infinitely, browser freezes
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
Source:
GitHub Discussions #2642 Issue #2863
Why It Happens: Creating new object references in selectors causes Zustand to think state changed.
v5 Breaking Change: Zustand v5 made this error MORE explicit compared to v4. In v4, this behavior was "non-ideal" but could go unnoticed. In v5, you'll immediately see the "Maximum update depth exceeded" error.
Prevention:
import { useShallow } from 'zustand/shallow'
// ❌ WRONG - Creates new object every time const { bears, fishes } = useStore((state) => ({ bears: state.bears, fishes: state.fishes, }))
// ✅ CORRECT Option 1 - Select primitives separately const bears = useStore((state) => state.bears) const fishes = useStore((state) => state.fishes)
// ✅ CORRECT Option 2 - Use useShallow hook for multiple values const { bears, fishes } = useStore( useShallow((state) => ({ bears: state.bears, fishes: state.fishes })) )
Issue #5: Slices Pattern TypeScript Complexity
Error: StateCreator types fail to infer, complex middleware types break
Source: Official Slices Pattern Guide
Why It Happens: Combining multiple slices requires explicit type annotations for middleware compatibility.
Prevention:
import { create, StateCreator } from 'zustand'
// Define slice types interface BearSlice { bears: number addBear: () => void }
interface FishSlice { fishes: number addFish: () => void }
// Create slices with proper types const createBearSlice: StateCreator< BearSlice & FishSlice, // Combined store type [], // Middleware mutators (empty if none) [], // Chained middleware (empty if none) BearSlice // This slice's type
= (set) => ({ bears: 0, addBear: () => set((state) => ({ bears: state.bears + 1 })), })
const createFishSlice: StateCreator< BearSlice & FishSlice, [], [], FishSlice
= (set) => ({ fishes: 0, addFish: () => set((state) => ({ fishes: state.fishes + 1 })), })
// Combine slices
const useStore = create
Issue #6: Persist Middleware Race Condition (Fixed v5.0.10+)
Error: Inconsistent state during concurrent rehydration attempts
Source:
GitHub PR #3336 Release v5.0.10
Why It Happens: In Zustand v5.0.9 and earlier, concurrent calls to rehydrate during persist middleware initialization could cause a race condition where multiple hydration attempts would interfere with each other, leading to inconsistent state.
Prevention: Upgrade to Zustand v5.0.10 or later. No code changes needed - the fix is internal to the persist middleware.
npm install zustand@latest # Ensure v5.0.10+
Note: This was fixed in v5.0.10 (January 2026). If you're using v5.0.9 or earlier and experiencing state inconsistencies with persist middleware, upgrade immediately.
Middleware
Persist (localStorage):
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create
Devtools (Redux DevTools):
import { devtools } from 'zustand/middleware'
const useStore = create
v4→v5 Migration Note: In Zustand v4, devtools was imported from 'zustand/middleware/devtools'. In v5, use 'zustand/middleware' (as shown above). If you see "Module not found: Can't resolve 'zustand/middleware/devtools'", update your import path.
Combining Middlewares (order matters):
const useStore = create
Common Patterns
Computed/Derived Values (in selector, not stored):
const count = useStore((state) => state.items.length) // Computed on read
Async Actions:
const useAsyncStore = create
Resetting Store:
const initialState = { count: 0, name: '' }
const useStore = create
Selector with Params:
const todo = useStore((state) => state.todos.find((t) => t.id === id))
Bundled Resources
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.ts
References: middleware-guide.md (persist/devtools/immer/custom), typescript-patterns.md (type inference issues), nextjs-hydration.md (SSR/hydration), migration-guide.md (from Redux/Context/v4)
Scripts: check-versions.sh (version compatibility)
Advanced Topics
Vanilla Store (Without React):
import { createStore } from 'zustand/vanilla'
const store = createStore
Custom Middleware:
const logger: Logger = (f, name) => (set, get, store) => {
const loggedSet: typeof set = (...a) => { set(...a); console.log([${name}]:, get()) }
return f(loggedSet, get, store)
}
Immer Middleware (Mutable Updates):
import { immer } from 'zustand/middleware/immer'
const useStore = create
v5.0.3→v5.0.4 Migration Note: If upgrading from v5.0.3 to v5.0.4+ and immer middleware stops working, verify you're using the import path shown above (zustand/middleware/immer). Some users reported issues after the v5.0.4 update that were resolved by confirming the correct import.
Experimental SSR Safe Middleware (v5.0.9+):
Status: Experimental (API may change)
Zustand v5.0.9 introduced experimental unstable_ssrSafe middleware for Next.js usage. This provides an alternative approach to the _hasHydrated pattern (see Issue #1).
import { unstable_ssrSafe } from 'zustand/middleware'
const useStore = create
Recommendation: Continue using the _hasHydrated pattern documented in Issue #1 until this API stabilizes. Monitor Discussion #2740 for updates on when this becomes stable.
Official Documentation Zustand: https://zustand.docs.pmnd.rs/ GitHub: https://github.com/pmndrs/zustand TypeScript Guide: https://zustand.docs.pmnd.rs/guides/typescript Context7 Library ID: /pmndrs/zustand