Claude-skills zustand-state-management

Zustand state management for React with TypeScript. Use for global state, Redux/Context API migration, localStorage persistence, slices pattern, devtools, Next.js SSR, or encountering hydration errors, TypeScript inference issues, persist middleware problems, infinite render loops.

install
source · Clone the upstream repo
git clone https://github.com/secondsky/claude-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/secondsky/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/zustand-state-management/skills/zustand-state-management" ~/.claude/skills/secondsky-claude-skills-zustand-state-management && rm -rf "$T"
manifest: plugins/zustand-state-management/skills/zustand-state-management/SKILL.md
source content

Zustand State Management

Status: Production Ready ✅ Last Updated: 2025-11-21 Latest Version: zustand@5.0.8 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 })),
}))

When to use:

  • 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 })),
}))

Key Points:

  • 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
    },
  ),
)

Why this matters:

  • 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)

IssueErrorQuick Fix
#1 Hydration mismatch"Text content does not match"Use
_hasHydrated
flag +
onRehydrateStorage
#2 TypeScript inferenceTypes break with middlewareUse
create<T>()()
double parentheses
#3 Import error"createJSONStorage not exported"Upgrade to zustand@5.0.8+
#4 Infinite loopBrowser freezesUse
shallow
or separate selectors
#5 Slices typesStateCreator types failExplicit
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' },
  ),
)
MiddlewarePurposeImport
persist
localStorage/sessionStorage
zustand/middleware
devtools
Redux DevTools integration
zustand/middleware
immer
Mutable 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

PatternUse CaseKey Technique
Computed valuesDerived dataCompute in selector:
state.items.length
Async actionsAPI calls
set({ isLoading: true })
+ try/catch
Reset storeLogout, form clear
set(initialState)
Selector with paramsDynamic access
state.todos.find(t => t.id === id)
Multiple storesSeparation of concernsCreate separate
create()
calls

See:

references/common-patterns.md
for complete implementations.


Advanced Topics

TopicUse CaseKey API
Vanilla storeNon-React, testing
createStore()
from
zustand/vanilla
Custom middlewareLogging, timestampsWrap
StateCreator
ImmerMutable update syntax
immer()
middleware
SubscriptionsSide effects
store.subscribe()

See:

references/advanced-topics.md
for complete implementations.


Bundled Resources

TypeFiles
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
,
typescript-patterns.md
,
nextjs-hydration.md
,
migration-guide.md
,
known-issues.md
,
common-patterns.md
,
advanced-topics.md

When to Load References

ReferenceLoad When...
known-issues.md
Debugging hydration, TypeScript, infinite loop, or slices errors
common-patterns.md
Implementing computed values, async actions, reset patterns
advanced-topics.md
Vanilla stores, custom middleware, Immer, subscriptions
middleware-guide.md
Configuring persist, devtools, or combining middlewares
typescript-patterns.md
Complex type inference issues, StateCreator problems
nextjs-hydration.md
Next.js SSR/hydration problems
migration-guide.md
Migrating from Redux, Context API, or Zustand v4

Quick Troubleshooting

ProblemSolution
Store updates don't trigger re-rendersUse selector:
useStore(state => state.value)
not destructuring
TypeScript errors with middlewareUse
create<T>()()
double parentheses
Hydration error with persistImplement
_hasHydrated
flag pattern
Actions not showing in DevToolsPass action name:
set(newState, undefined, 'actionName')
Store resets unexpectedlyHMR causes reset in development

Dependencies

{ "dependencies": { "zustand": "^5.0.8", "react": "^18.0.0+" } }

Compatibility: React 18+, React 19, TypeScript 5+, Next.js 14+, Vite 5+


Official Docs: https://zustand.docs.pmnd.rs/ | GitHub: https://github.com/pmndrs/zustand