**Last Updated**: 2026-01-21 **Latest Version**: zustand@5.0.10 (released 2026-01-12) **Dependencies**: React 18-19, TypeScript 5+ * * * ```
npm install zustandcreate<T>()() double parentheses):import { create } from 'zustand' interface BearStore { bears: number increase: (by: number) => void } const useBearStore = create<BearStore>()((set) => ({ bears: 0, increase: (by) => set((state) => ({ bears: state.bears + by })), })) `**Use in Components**:` const bears = useBearStore((state) => state.bears) // Only re-renders when bears changes const increase = useBearStore((state) => state.increase)
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<CounterStore>()((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })) `**Persistent Store** (survives page reloads):` import { persist, createJSONStorage } from 'zustand/middleware' const useStore = create<UserPreferences>()( persist( (set) => ({ theme: 'system', setTheme: (theme) => set({ theme }) }), { name: 'user-preferences', storage: createJSONStorage(() => localStorage) }, ), )
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 useShallow hook for selecting multiple values ✅ Keep actions pure (no side effects except state updates)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"Text content does not match server-rendered HTML" or "Hydration failed"import { create } from 'zustand' import { persist } from 'zustand/middleware' interface StoreWithHydration { count: number _hasHydrated: boolean setHasHydrated: (hydrated: boolean) => void increase: () => void } const useStore = create<StoreWithHydration>()( persist( (set) => ({ count: 0, _hasHydrated: false, setHasHydrated: (hydrated) => set({ _hasHydrated: hydrated }), increase: () => set((state) => ({ count: state.count + 1 })), }), { name: 'my-store', onRehydrateStorage: () => (state) => { state?.setHasHydrated(true) }, }, ), ) // In component function MyComponent() { const hasHydrated = useStore((state) => state._hasHydrated) if (!hasHydrated) { return <div>Loading...</div> } // Now safe to render with persisted state return <ActualContent /> }
StateCreator types break with middlewarecreate<T>()() is required for middleware to work with TypeScript inference.// ❌ WRONG - Single parentheses const useStore = create<MyStore>((set) => ({ // ... })) // ✅ CORRECT - Double parentheses const useStore = create<MyStore>()((set) => ({ // ... }))
create<T>()() in TypeScript, even without middleware (future-proof)."Attempted import error: 'createJSONStorage' is not exported from 'zustand/middleware'"// ✅ 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"
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.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 })) )
StateCreator types fail to infer, complex middleware types breakimport { 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<BearSlice & FishSlice>()((...a) => ({ ...createBearSlice(...a), ...createFishSlice(...a), }))
npm install zustand@latest # Ensure v5.0.10+import { persist, createJSONStorage } from 'zustand/middleware' const useStore = create<MyStore>()( persist( (set) => ({ data: [], addItem: (item) => set((state) => ({ data: [...state.data, item] })) }), { name: 'my-storage', partialize: (state) => ({ data: state.data }), // Only persist 'data' }, ), ) `**Devtools** (Redux DevTools):` import { devtools } from 'zustand/middleware' const useStore = create<CounterStore>()( devtools( (set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 }), undefined, 'increment') }), { name: 'CounterStore' }, ), )
'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.const useStore = create<MyStore>()(devtools(persist((set) => ({ /* ... */ }), { name: 'storage' }), { name: 'MyStore' }))const count = useStore((state) => state.items.length) // Computed on readconst useAsyncStore = create<AsyncStore>()((set) => ({ data: null, isLoading: false, fetchData: async () => { set({ isLoading: true }) const response = await fetch('/api/data') set({ data: await response.text(), isLoading: false }) }, })) `**Resetting Store**:` const initialState = { count: 0, name: '' } const useStore = create<ResettableStore>()((set) => ({ ...initialState, reset: () => set(initialState), })) `**Selector with Params**:` const todo = useStore((state) => state.todos.find((t) => t.id === id))
basic-store.ts, typescript-store.ts, persist-store.ts, slices-pattern.ts, devtools-store.ts, nextjs-store.ts, computed-store.ts, async-actions-store.tsmiddleware-guide.md (persist/devtools/immer/custom), typescript-patterns.md (type inference issues), nextjs-hydration.md (SSR/hydration), migration-guide.md (from Redux/Context/v4)check-versions.sh (version compatibility)import { createStore } from 'zustand/vanilla' const store = createStore<CounterStore>()((set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 })) })) const unsubscribe = store.subscribe((state) => console.log(state.count)) store.getState().increment() `**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<TodoStore>()(immer((set) => ({ todos: [], addTodo: (text) => set((state) => { state.todos.push({ id: Date.now().toString(), text }) }), })))
zustand/middleware/immer). Some users reported issues after the v5.0.4 update that were resolved by confirming the correct import.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<Store>()( unstable_ssrSafe( persist( (set) => ({ /* state */ }), { name: 'my-store' } ) ) )
_hasHydrated pattern documented in Issue #1 until this API stabilizes. Monitor Discussion #2740 for updates on when this becomes stable./pmndrs/zustand