Skip to content
Development
Skill

/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

From plugin
secondsky-claude-skills
219183 skills42 agents62 commands2 MCP
Install
$ npx -y skills add secondsky/claude-skills --skill zustand-state-management --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/zustand-state-management

Context preview

The summary Claude sees to decide when to auto-load this skill.

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

SKILL.md

zustand-state-management.SKILL.md
name: zustand-state-management
description: "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."

metadata:
  keywords:
    - zustand
    - state management
    - React state
    - TypeScript state
    - persist middleware
    - devtools
    - slices pattern
    - global state
    - React hooks
    - create store
    - useBoundStore
    - StateCreator
    - hydration error
    - text content mismatch
    - infinite render
    - localStorage
    - sessionStorage
    - immer middleware
    - shallow equality
    - selector pattern
    - zustand v5

license: MIT

Zustand State Management

**Status**: Production Ready ✅ **Last Updated**: 2026-08-03 **Latest Version**: zustand@5.0.14 **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)

| 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 zustand@5.0.14+ | | **#4 Infinite loop** | Browser freezes | Use `shallow` or separate selectors | | *

Read more
Ships withsecondsky-claude-skills

145 production-ready skills for Claude Code CLI 🔌 Platform / Harness Support These plugins ship as Claude Code marketplace plugins (.claude-plugin/ manifests) and Codex CLI plugins (.codex-plugin/ manifests).

Get the whole plugin

Other skills on secondsky-claude-skills.