agentskills.codes
ZU

zustand-state-builder

Implements lightweight state management using Zustand with TypeScript, persistence, devtools, and modular store patterns. Use when users request "zustand store", "state management", "global state", "zustand setup", or "jotai alternative".

Install

mkdir -p .claude/skills/zustand-state-builder && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14199" && unzip -o skill.zip -d .claude/skills/zustand-state-builder && rm skill.zip

Installs to .claude/skills/zustand-state-builder

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Implements lightweight state management using Zustand with TypeScript, persistence, devtools, and modular store patterns. Use when users request "zustand store", "state management", "global state", "zustand setup", or "jotai alternative".
238 chars✓ has a “when” trigger

About this skill

Zustand State Builder

Build lightweight, scalable state management with Zustand's minimal API.

Core Workflow

  1. Identify state needs: Determine what needs global state
  2. Create store: Define state shape and actions
  3. Add TypeScript types: Full type safety
  4. Enable middleware: Devtools, persist, immer
  5. Split stores: Modular slices for large apps
  6. Connect components: Use hooks to access state

Installation

npm install zustand

# Optional middleware
npm install immer  # For immutable updates

Basic Store

Simple Counter Store

// stores/counter.ts
import { create } from 'zustand';

interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
  incrementBy: (amount: number) => void;
}

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

// Usage in component
function Counter() {
  const { count, increment, decrement } = useCounterStore();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

Async Actions

// stores/users.ts
import { create } from 'zustand';

interface User {
  id: string;
  name: string;
  email: string;
}

interface UsersState {
  users: User[];
  isLoading: boolean;
  error: string | null;
  fetchUsers: () => Promise<void>;
  addUser: (user: Omit<User, 'id'>) => Promise<void>;
  deleteUser: (id: string) => Promise<void>;
}

export const useUsersStore = create<UsersState>((set, get) => ({
  users: [],
  isLoading: false,
  error: null,

  fetchUsers: async () => {
    set({ isLoading: true, error: null });
    try {
      const response = await fetch('/api/users');
      const users = await response.json();
      set({ users, isLoading: false });
    } catch (error) {
      set({ error: 'Failed to fetch users', isLoading: false });
    }
  },

  addUser: async (userData) => {
    set({ isLoading: true, error: null });
    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(userData),
      });
      const newUser = await response.json();
      set((state) => ({
        users: [...state.users, newUser],
        isLoading: false,
      }));
    } catch (error) {
      set({ error: 'Failed to add user', isLoading: false });
    }
  },

  deleteUser: async (id) => {
    const previousUsers = get().users;
    // Optimistic update
    set((state) => ({
      users: state.users.filter((u) => u.id !== id),
    }));
    try {
      await fetch(`/api/users/${id}`, { method: 'DELETE' });
    } catch (error) {
      // Rollback on error
      set({ users: previousUsers, error: 'Failed to delete user' });
    }
  },
}));

Middleware

DevTools Integration

import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

interface StoreState {
  count: number;
  increment: () => void;
}

export const useStore = create<StoreState>()(
  devtools(
    (set) => ({
      count: 0,
      increment: () =>
        set(
          (state) => ({ count: state.count + 1 }),
          false,
          'increment' // Action name for devtools
        ),
    }),
    { name: 'CounterStore' } // Store name in devtools
  )
);

Persistence

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

interface SettingsState {
  theme: 'light' | 'dark';
  language: string;
  notifications: boolean;
  setTheme: (theme: 'light' | 'dark') => void;
  setLanguage: (language: string) => void;
  toggleNotifications: () => void;
}

export const useSettingsStore = create<SettingsState>()(
  persist(
    (set) => ({
      theme: 'light',
      language: 'en',
      notifications: true,
      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language }),
      toggleNotifications: () =>
        set((state) => ({ notifications: !state.notifications })),
    }),
    {
      name: 'settings-storage', // localStorage key
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({
        // Only persist these fields
        theme: state.theme,
        language: state.language,
        notifications: state.notifications,
      }),
    }
  )
);

Immer Middleware

import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

interface TodosState {
  todos: Todo[];
  addTodo: (text: string) => void;
  toggleTodo: (id: string) => void;
  updateTodo: (id: string, text: string) => void;
  deleteTodo: (id: string) => void;
}

export const useTodosStore = create<TodosState>()(
  immer((set) => ({
    todos: [],

    addTodo: (text) =>
      set((state) => {
        state.todos.push({
          id: crypto.randomUUID(),
          text,
          completed: false,
        });
      }),

    toggleTodo: (id) =>
      set((state) => {
        const todo = state.todos.find((t) => t.id === id);
        if (todo) {
          todo.completed = !todo.completed;
        }
      }),

    updateTodo: (id, text) =>
      set((state) => {
        const todo = state.todos.find((t) => t.id === id);
        if (todo) {
          todo.text = text;
        }
      }),

    deleteTodo: (id) =>
      set((state) => {
        const index = state.todos.findIndex((t) => t.id === id);
        if (index !== -1) {
          state.todos.splice(index, 1);
        }
      }),
  }))
);

Combined Middleware

import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

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

Slices Pattern

Modular Store Architecture

// stores/slices/authSlice.ts
import { StateCreator } from 'zustand';

export interface AuthSlice {
  user: User | null;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

export const createAuthSlice: StateCreator<
  AuthSlice & CartSlice, // Combined state type
  [],
  [],
  AuthSlice
> = (set) => ({
  user: null,
  isAuthenticated: false,

  login: async (email, password) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    const user = await response.json();
    set({ user, isAuthenticated: true });
  },

  logout: () => set({ user: null, isAuthenticated: false }),
});
// stores/slices/cartSlice.ts
import { StateCreator } from 'zustand';

interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

export interface CartSlice {
  items: CartItem[];
  addItem: (item: Omit<CartItem, 'quantity'>) => void;
  removeItem: (id: string) => void;
  updateQuantity: (id: string, quantity: number) => void;
  clearCart: () => void;
  totalItems: () => number;
  totalPrice: () => number;
}

export const createCartSlice: StateCreator<
  AuthSlice & CartSlice,
  [],
  [],
  CartSlice
> = (set, get) => ({
  items: [],

  addItem: (item) =>
    set((state) => {
      const existing = state.items.find((i) => i.id === item.id);
      if (existing) {
        return {
          items: state.items.map((i) =>
            i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
          ),
        };
      }
      return { items: [...state.items, { ...item, quantity: 1 }] };
    }),

  removeItem: (id) =>
    set((state) => ({
      items: state.items.filter((i) => i.id !== id),
    })),

  updateQuantity: (id, quantity) =>
    set((state) => ({
      items:
        quantity <= 0
          ? state.items.filter((i) => i.id !== id)
          : state.items.map((i) => (i.id === id ? { ...i, quantity } : i)),
    })),

  clearCart: () => set({ items: [] }),

  totalItems: () => get().items.reduce((sum, i) => sum + i.quantity, 0),

  totalPrice: () =>
    get().items.reduce((sum, i) => sum + i.price * i.quantity, 0),
});
// stores/index.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { createAuthSlice, AuthSlice } from './slices/authSlice';
import { createCartSlice, CartSlice } from './slices/cartSlice';

type StoreState = AuthSlice & CartSlice;

export const useStore = create<StoreState>()(
  devtools(
    persist(
      (...args) => ({
        ...createAuthSlice(...args),
        ...createCartSlice(...args),
      }),
      {
        name: 'app-store',
        partialize: (state) => ({
          items: state.items, // Persist cart
          // Don't persist auth (handle with tokens)
        }),
      }
    ),
    { name: 'AppStore' }
  )
);

Selectors

Optimized Selectors

// Avoid re-renders with selectors
function UserName() {
  // Only re-renders when user.name changes
  const userName = useStore((state) => state.user?.name);
  return <span>{userName}</span>;
}

// Multiple values with shallow comparison
import { shallow } from 'zustand/shallow';

function UserInfo() {
  const { name, email } = useStore(
    (state) => ({ name: state.user?.name, email: state.user?.email }),
    shallow
  );
  return (
    <div>
      <p>{name}</p>
      <p>{email}</p>
    </div>
  );
}

// Computed values
function CartSummary() {
  const totalItems = useStore((state) =>
    state.items.reduce((sum, i) => sum + i.quantity, 0)
  );
  const totalPrice = useStore((sta

---

*Content truncated.*

Search skills

Search the agent skills registry