Zustand state management for React. Covers stores, actions, and persistence. Use for simple global state management. USE WHEN: user mentions "zustand", "global state", "store", asks about "simple state management", "lightweight state", "create store", "persist state", "middleware", "devtools integration" DO NOT USE FOR: server data - use `tanstack-query` or `swr` instead; Vue apps - use `pinia`; complex async workflows - use `redux-toolkit`

GitHub
Install command
npx skhub add claude-dev-suite/zustand
Markdown
SKILL.md

Zustand Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: zustand for comprehensive documentation.

Basic Store

import { create } from 'zustand';

interface CounterStore {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

const useCounterStore = create<CounterStore>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

// Usage
function Counter() {
  const { count, increment } = useCounterStore();
  return <button onClick={increment}>{count}</button>;
}

Async Actions

interface UserStore {
  user: User | null;
  loading: boolean;
  error: string | null;
  fetchUser: (id: string) => Promise<void>;
}

const useUserStore = create<UserStore>((set) => ({
  user: null,
  loading: false,
  error: null,
  fetchUser: async (id) => {
    set({ loading: true, error: null });
    try {
      const user = await api.getUser(id);
      set({ user, loading: false });
    } catch (err) {
      set({ error: err.message, loading: false });
    }
  },
}));

Selectors

// Select specific state (prevents unnecessary re-renders)
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);

// Shallow comparison for objects
import { shallow } from 'zustand/shallow';
const { user, loading } = useUserStore(
  (state) => ({ user: state.user, loading: state.loading }),
  shallow
);

Persist Middleware

import { persist } from 'zustand/middleware';

const useStore = create(
  persist<MyStore>(
    (set) => ({
      // ... state and actions
    }),
    {
      name: 'my-store',
      partialize: (state) => ({ count: state.count }), // Only persist count
    }
  )
);

DevTools

import { devtools } from 'zustand/middleware';

const useStore = create(
  devtools<MyStore>((set) => ({
    // ... state and actions
  }), { name: 'MyStore' })
);

When NOT to Use This Skill

ScenarioUse Instead
Server state management (API data, caching)tanstack-query or swr
Vue 3 applicationspinia
Complex async workflows with side effectsredux-toolkit
Form state managementReact Hook Form or Formik
URL-based state (routing)React Router or Next.js router

Anti-Patterns

Anti-PatternWhy It's BadCorrect Approach
Storing server data in ZustandNo cache invalidation, manual refetchingUse TanStack Query or SWR
Creating multiple stores for everythingIncreases complexity unnecessarilyUse slices or combine related state
Mutating state without set()Breaks reactivityAlways use set() or immer middleware
Storing derived stateDuplicates data, sync issuesUse selectors with computation
Not using selectorsUnnecessary re-rendersUse atomic selectors for each value
Persisting sensitive data unencryptedSecurity vulnerabilityEncrypt with createJSONStorage custom storage
Using stores outside React componentsMemory leaks, testing issuesKeep store access in components/hooks
Not resetting state on logoutData leaks between usersCall setState(initialState) or $reset()

Quick Troubleshooting

IssueCauseSolution
Component not re-renderingNot using selector or wrong selectorUse (state) => state.value selector
State updates not persistingPersist middleware not configuredAdd persist() middleware with storage
"Cannot read property of undefined"State hydration race conditionAdd skipHydration check or loading state
Multiple re-rendersSelecting entire state objectUse shallow equality or atomic selectors
Tests failing with store stateStore state persists between testsReset with setState() in beforeEach()
DevTools not workingMiddleware order incorrectWrap with devtools() as outer middleware
Memory leaksSubscriptions not cleaned upUse store.subscribe() with cleanup
TypeScript errors with middlewareWrong generic orderFollow create<T>()(middleware(...)) pattern

Production Readiness

Store Organization

// stores/userStore.ts - Typed store with slices
import { create } from 'zustand';
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

interface UserState {
  user: User | null;
  isAuthenticated: boolean;
}

interface UserActions {
  setUser: (user: User | null) => void;
  logout: () => void;
}

type UserStore = UserState & UserActions;

const initialState: UserState = {
  user: null,
  isAuthenticated: false,
};

export const useUserStore = create<UserStore>()(
  devtools(
    persist(
      subscribeWithSelector(
        immer((set) => ({
          ...initialState,
          setUser: (user) =>
            set((state) => {
              state.user = user;
              state.isAuthenticated = !!user;
            }),
          logout: () => set(initialState),
        }))
      ),
      {
        name: 'user-store',
        partialize: (state) => ({ user: state.user }),
        // Don't persist to localStorage in SSR
        skipHydration: typeof window === 'undefined',
      }
    ),
    { name: 'UserStore', enabled: process.env.NODE_ENV === 'development' }
  )
);

Security Best Practices

// Secure persistence with encryption
import { persist, createJSONStorage } from 'zustand/middleware';
import CryptoJS from 'crypto-js';

const SECRET_KEY = process.env.NEXT_PUBLIC_STORE_KEY!;

const encryptedStorage = {
  getItem: (name: string) => {
    const encrypted = localStorage.getItem(name);
    if (!encrypted) return null;
    const decrypted = CryptoJS.AES.decrypt(encrypted, SECRET_KEY);
    return decrypted.toString(CryptoJS.enc.Utf8);
  },
  setItem: (name: string, value: string) => {
    const encrypted = CryptoJS.AES.encrypt(value, SECRET_KEY).toString();
    localStorage.setItem(name, encrypted);
  },
  removeItem: (name: string) => localStorage.removeItem(name),
};

export const useAuthStore = create(
  persist(
    (set) => ({ token: null }),
    {
      name: 'auth-store',
      storage: createJSONStorage(() => encryptedStorage),
    }
  )
);

Testing Stores

// Store testing with isolated state
import { act, renderHook } from '@testing-library/react';
import { useUserStore } from './userStore';

describe('UserStore', () => {
  beforeEach(() => {
    // Reset store before each test
    useUserStore.setState({ user: null, isAuthenticated: false });
  });

  it('should set user and authenticate', () => {
    const { result } = renderHook(() => useUserStore());

    act(() => {
      result.current.setUser({ id: '1', name: 'John' });
    });

    expect(result.current.user?.name).toBe('John');
    expect(result.current.isAuthenticated).toBe(true);
  });

  it('should logout and clear state', () => {
    useUserStore.setState({ user: { id: '1', name: 'John' }, isAuthenticated: true });

    const { result } = renderHook(() => useUserStore());

    act(() => {
      result.current.logout();
    });

    expect(result.current.user).toBeNull();
    expect(result.current.isAuthenticated).toBe(false);
  });
});

Performance Optimization

// Atomic selectors to prevent unnecessary re-renders
const userName = useUserStore((state) => state.user?.name);
const isAuthenticated = useUserStore((state) => state.isAuthenticated);

// createSelectors helper for auto-generated selectors
import { StoreApi, UseBoundStore } from 'zustand';

type WithSelectors<S> = S extends { getState: () => infer T }
  ? S & { use: { [K in keyof T]: () => T[K] } }
  : never;

const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(
  _store: S
) => {
  const store = _store as WithSelectors<typeof _store>;
  store.use = {};
  for (const k of Object.keys(store.getState())) {
    (store.use as any)[k] = () => store((s) => s[k as keyof typeof s]);
  }
  return store;
};

// Usage
export const useUserStore = createSelectors(useUserStoreBase);
const userName = useUserStore.use.user()?.name;

Monitoring Metrics

MetricTarget
Store re-render countMinimal
Hydration time< 50ms
Bundle size impact< 5KB
Test coverage> 90%

Checklist

  • TypeScript types for state and actions
  • Devtools enabled (dev only)
  • Atomic selectors for performance
  • Persist sensitive data encrypted
  • SSR hydration handled
  • Store reset on logout
  • Immer for complex updates
  • subscribeWithSelector for reactions
  • Comprehensive unit tests
  • No sensitive data in plain localStorage

Reference Documentation

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/state-management/zustand

Default branch

main

Latest commit

9496306

Tree SHA

fe4e2f1