skills/zustand_state/SKILL.md
Production-tested setup for Zustand state management in React. Includes patterns for persistence, devtools, and TypeScript patterns. Prevents hydration mismatches and render loops.
npx skillsauth add vuralserhat86/antigravity-agentic-skills zustand_stateInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Status: Production Ready ✅ Last Updated: 2025-10-24 Latest Version: [email protected] Dependencies: React 18+, TypeScript 5+
npm install zustand
# or
pnpm add zustand
# or
yarn add zustand
Why Zustand?
create)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.
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:
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:
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:
create<T>()() syntax (currying for middleware)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:
✅ 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)
❌ 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
This skill prevents 5 documented issues:
Error: "Text content does not match server-rendered HTML" or "Hydration failed"
Source:
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<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 />
}
Error: Type inference fails, StateCreator types break with middleware
Source: Official Zustand TypeScript Guide
Why It Happens:
The currying syntax create<T>()() is required for middleware to work with TypeScript inference.
Prevention:
// ❌ WRONG - Single parentheses
const useStore = create<MyStore>((set) => ({
// ...
}))
// ✅ CORRECT - Double parentheses
const useStore = create<MyStore>()((set) => ({
// ...
}))
Rule: Always use create<T>()() in TypeScript, even without middleware (future-proof).
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
// [email protected] includes createJSONStorage
// [email protected] uses different API
// Check your package.json
// "zustand": "^5.0.8"
Error: Component re-renders infinitely, browser freezes
Source: GitHub Discussions #2642
Why It Happens: Creating new object references in selectors causes Zustand to think state changed.
Prevention:
import { shallow } 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 shallow for multiple values
const { bears, fishes } = useStore(
(state) => ({ bears: state.bears, fishes: state.fishes }),
shallow,
)
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<BearSlice & FishSlice>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
interface MyStore {
data: string[]
addItem: (item: string) => void
}
const useStore = create<MyStore>()(
persist(
(set) => ({
data: [],
addItem: (item) => set((state) => ({ data: [...state.data, item] })),
}),
{
name: 'my-storage',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({ data: state.data }), // Only persist 'data'
},
),
)
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
interface CounterStore {
count: number
increment: () => void
}
const useStore = create<CounterStore>()(
devtools(
(set) => ({
count: 0,
increment: () =>
set(
(state) => ({ count: state.count + 1 }),
undefined,
'counter/increment', // Action name in DevTools
),
}),
{ name: 'CounterStore' }, // Store name in DevTools
),
)
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
const useStore = create<MyStore>()(
devtools(
persist(
(set) => ({
// store definition
}),
{ name: 'my-storage' },
),
{ name: 'MyStore' },
),
)
Order matters: devtools(persist(...)) shows persist actions in DevTools.
interface StoreWithComputed {
items: string[]
addItem: (item: string) => void
// Computed in selector, not stored
}
const useStore = create<StoreWithComputed>()((set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
}))
// Use in component
function ItemCount() {
const count = useStore((state) => state.items.length)
return <div>{count} items</div>
}
interface AsyncStore {
data: string | null
isLoading: boolean
error: string | null
fetchData: () => Promise<void>
}
const useAsyncStore = create<AsyncStore>()((set) => ({
data: null,
isLoading: false,
error: null,
fetchData: async () => {
set({ isLoading: true, error: null })
try {
const response = await fetch('/api/data')
const data = await response.text()
set({ data, isLoading: false })
} catch (error) {
set({ error: (error as Error).message, isLoading: false })
}
},
}))
interface ResettableStore {
count: number
name: string
increment: () => void
reset: () => void
}
const initialState = {
count: 0,
name: '',
}
const useStore = create<ResettableStore>()((set) => ({
...initialState,
increment: () => set((state) => ({ count: state.count + 1 })),
reset: () => set(initialState),
}))
interface TodoStore {
todos: Array<{ id: string; text: string; done: boolean }>
addTodo: (text: string) => void
toggleTodo: (id: string) => void
}
const useStore = create<TodoStore>()((set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [...state.todos, { id: Date.now().toString(), text, done: false }],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo
),
})),
}))
// Use with parameter
function Todo({ id }: { id: string }) {
const todo = useStore((state) => state.todos.find((t) => t.id === id))
const toggleTodo = useStore((state) => state.toggleTodo)
if (!todo) return null
return (
<div>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(id)}
/>
{todo.text}
</div>
)
}
This skill includes 8 ready-to-use template files:
basic-store.ts - Minimal JavaScript store exampletypescript-store.ts - Properly typed TypeScript storepersist-store.ts - localStorage persistence with migrationslices-pattern.ts - Modular store organizationdevtools-store.ts - Redux DevTools integrationnextjs-store.ts - SSR-safe Next.js store with hydrationcomputed-store.ts - Derived state patternsasync-actions-store.ts - Async operations with loading statesExample Usage:
# Copy template to your project
cp ~/.claude/skills/zustand-state-management/templates/typescript-store.ts src/store/
When to use each:
basic-store.ts for quick prototypestypescript-store.ts for most production appspersist-store.ts when state needs to survive page reloadsslices-pattern.ts for large, complex stores (100+ lines)nextjs-store.ts for Next.js projects with SSRDeep-dive documentation for complex scenarios:
middleware-guide.md - Complete middleware documentation (persist, devtools, immer, custom)typescript-patterns.md - Advanced TypeScript patterns and troubleshootingnextjs-hydration.md - SSR, hydration, and Next.js best practicesmigration-guide.md - Migrating from Redux, Context API, or Zustand v4When Claude should load these:
middleware-guide.md when user asks about persistence, devtools, or custom middlewaretypescript-patterns.md when encountering complex type inference issuesnextjs-hydration.md for Next.js-specific problemsmigration-guide.md when migrating from other state management solutionscheck-versions.sh - Verify Zustand version and compatibilityUsage:
cd your-project/
~/.claude/skills/zustand-state-management/scripts/check-versions.sh
import { createStore } from 'zustand/vanilla'
const store = createStore<CounterStore>()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
// Subscribe to changes
const unsubscribe = store.subscribe((state) => {
console.log('Count changed:', state.count)
})
// Get current state
console.log(store.getState().count)
// Update state
store.getState().increment()
// Cleanup
unsubscribe()
import { StateCreator, StoreMutatorIdentifier } from 'zustand'
type Logger = <T>(
f: StateCreator<T, [], []>,
name?: string,
) => StateCreator<T, [], []>
const logger: Logger = (f, name) => (set, get, store) => {
const loggedSet: typeof set = (...a) => {
set(...(a as Parameters<typeof set>))
console.log(`[${name}]:`, get())
}
return f(loggedSet, get, store)
}
// Use custom middleware
const useStore = create<MyStore>()(
logger((set) => ({
// store definition
}), 'MyStore'),
)
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface TodoStore {
todos: Array<{ id: string; text: string }>
addTodo: (text: string) => void
}
const useStore = create<TodoStore>()(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
// Mutate directly with Immer
state.todos.push({ id: Date.now().toString(), text })
}),
})),
)
Required:
[email protected] - State management library[email protected]+ - React frameworkOptional:
@types/node - For TypeScript path resolutionimmer - For mutable update syntax/pmndrs/zustand{
"dependencies": {
"zustand": "^5.0.8",
"react": "^19.0.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.0.0"
}
}
Compatibility:
Solution: Ensure you're using selector functions, not destructuring: const bears = useStore(state => state.bears) not const { bears } = useStore()
Solution: Use double parentheses: create<T>()() not create<T>()
Solution: Implement _hasHydrated flag pattern (see Issue #1)
Solution: Pass action name as third parameter to set: set(newState, undefined, 'actionName')
Solution: Check if using HMR (hot module replacement) - Zustand resets on module reload in development
Kaynak: Zustand v5.0 Official Documentation & Next.js Hydration Patterns
create<T>()() (double parentheses) syntax'ını kullanarak store'u başlat.persist (localStorage) veya devtools katmanlarını ekle.StateCreator kullanarak atomik dilimlere ayır.useStore(state => state.X)) sağla.shallow kullan._hasHydrated flag pattern'i ile SSR uyumluluğunu sağla.sessionStorage veya özel şifreli storage konfigürasyonunu yap.counter/increase) devtools ile izle.| Aşama | Doğrulama |
|-------|-----------|
| 1 | set metodu içerisinde doğrudan mutasyon yapıldı mı? (İllegal!) |
| 2 | Component içinde selector yerine doğrudan destructuring yapıldı mı? (Performans riski!) |
| 3 | Persist storage key'i benzersiz (Unique) mi? |
Zustand State v2.0 - With Workflow5. GitHub issues: https://github.com/pmndrs/zustand/issues
development
Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas
development
--- name: websocket_engineer router_kit: FullStackKit description: WebSocket specialist for real-time communication systems. Invoke for Socket.IO, WebSocket servers, bidirectional messaging, presence systems. Keywords: WebSocket, Socket.IO, real-time, pub/sub, Redis. triggers: - WebSocket - Socket.IO - real-time communication - bidirectional messaging - pub/sub - server push - live updates - chat systems - presence tracking role: specialist scope: implementation output-format:
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
tools
--- name: web_artifacts_builder router_kit: FullStackKit description: React, Tailwind, shadcn/ui ile karmaşık web artifacts oluşturma rehberi. metadata: skillport: category: development tags: [accessibility, api integration, backend, browser apis, client-side, components, css3, debugging, deployment, frameworks, frontend, fullstack, html5, javascript, libraries, node.js, npm, performance optimization, responsive design, seo, state management, testing, typescript, ui/ux, web artifacts b