home / skills / a5c-ai / babysitter / babysit

This skill helps you implement Zustand state management with type-safe stores, slices, and middleware for scalable React apps.

npx playbooks add skill a5c-ai/babysitter --skill babysit

Review the files below or copy the command above to add this skill to your agents.

Files (6939)
SKILL.md
19.6 KB
---
name: zustand
description: Zustand state management patterns including store creation, middleware, persistence, slices, and DevTools integration.
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
---

# Zustand Skill

Expert assistance for implementing Zustand state management with modern patterns and TypeScript.

## Capabilities

- Create type-safe Zustand stores
- Implement middleware (persist, devtools, immer)
- Design store slices for modular state
- Optimize selectors for performance
- Handle async actions and subscriptions
- Integrate with React components efficiently

## Usage

Invoke this skill when you need to:
- Set up lightweight global state
- Create stores with persistence
- Implement complex state with slices
- Optimize component re-renders with selectors
- Migrate from Redux or Context

## Inputs

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| storeName | string | Yes | Name of the store |
| stateShape | object | Yes | Initial state structure |
| actions | array | Yes | Store actions to create |
| middleware | array | No | Middleware to apply |
| persist | boolean | No | Enable persistence |

### Configuration Example

```json
{
  "storeName": "useCartStore",
  "stateShape": {
    "items": [],
    "total": 0
  },
  "actions": ["addItem", "removeItem", "clearCart"],
  "middleware": ["devtools", "persist"],
  "persist": true
}
```

## Generated Patterns

### Basic Store

```typescript
import { create } from 'zustand';

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

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

export const useCartStore = create<CartStore>((set, get) => ({
  items: [],
  total: 0,

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

  removeItem: (id) =>
    set((state) => {
      const item = state.items.find((i) => i.id === id);
      return {
        items: state.items.filter((i) => i.id !== id),
        total: state.total - (item ? item.price * item.quantity : 0),
      };
    }),

  updateQuantity: (id, quantity) =>
    set((state) => {
      const item = state.items.find((i) => i.id === id);
      if (!item) return state;
      const diff = (quantity - item.quantity) * item.price;
      return {
        items: state.items.map((i) =>
          i.id === id ? { ...i, quantity } : i
        ),
        total: state.total + diff,
      };
    }),

  clearCart: () => set({ items: [], total: 0 }),
}));
```

### Store with Middleware

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

interface UserStore {
  user: User | null;
  preferences: Preferences;
  setUser: (user: User) => void;
  updatePreferences: (prefs: Partial<Preferences>) => void;
  logout: () => void;
}

export const useUserStore = create<UserStore>()(
  devtools(
    persist(
      subscribeWithSelector(
        immer((set) => ({
          user: null,
          preferences: { theme: 'light', notifications: true },

          setUser: (user) =>
            set((state) => {
              state.user = user;
            }),

          updatePreferences: (prefs) =>
            set((state) => {
              Object.assign(state.preferences, prefs);
            }),

          logout: () =>
            set((state) => {
              state.user = null;
            }),
        }))
      ),
      {
        name: 'user-storage',
        partialize: (state) => ({ preferences: state.preferences }),
      }
    ),
    { name: 'UserStore' }
  )
);
```

### Slice Pattern

```typescript
import { create, StateCreator } from 'zustand';

interface AuthSlice {
  isAuthenticated: boolean;
  token: string | null;
  login: (token: string) => void;
  logout: () => void;
}

interface UISlice {
  sidebarOpen: boolean;
  theme: 'light' | 'dark';
  toggleSidebar: () => void;
  setTheme: (theme: 'light' | 'dark') => void;
}

type StoreState = AuthSlice & UISlice;

const createAuthSlice: StateCreator<StoreState, [], [], AuthSlice> = (set) => ({
  isAuthenticated: false,
  token: null,
  login: (token) => set({ isAuthenticated: true, token }),
  logout: () => set({ isAuthenticated: false, token: null }),
});

const createUISlice: StateCreator<StoreState, [], [], UISlice> = (set) => ({
  sidebarOpen: true,
  theme: 'light',
  toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
  setTheme: (theme) => set({ theme }),
});

export const useAppStore = create<StoreState>()((...a) => ({
  ...createAuthSlice(...a),
  ...createUISlice(...a),
}));
```

### Async Actions

```typescript
import { create } from 'zustand';

interface DataStore {
  data: Item[];
  loading: boolean;
  error: string | null;
  fetchData: () => Promise<void>;
}

export const useDataStore = create<DataStore>((set, get) => ({
  data: [],
  loading: false,
  error: null,

  fetchData: async () => {
    set({ loading: true, error: null });
    try {
      const response = await fetch('/api/data');
      const data = await response.json();
      set({ data, loading: false });
    } catch (error) {
      set({ error: (error as Error).message, loading: false });
    }
  },
}));
```

## Selector Patterns

```typescript
// Shallow comparison for object selectors
import { shallow } from 'zustand/shallow';

const { items, total } = useCartStore(
  (state) => ({ items: state.items, total: state.total }),
  shallow
);

// Computed selectors
const itemCount = useCartStore((state) =>
  state.items.reduce((sum, item) => sum + item.quantity, 0)
);
```

## Best Practices

- Use selectors to minimize re-renders
- Apply persist middleware for state that should survive refreshes
- Use immer for complex nested updates
- Split large stores into slices
- Keep actions colocated with state

## Target Processes

- react-application-development
- state-management-setup
- nextjs-full-stack
- t3-stack-development

Overview

This skill provides practical patterns and TypeScript-ready examples for building Zustand stores, middleware composition, persistence, slices, and DevTools integration. It focuses on type-safe store creation, selector optimization, async actions, and smooth React integration to reduce boilerplate and improve runtime performance. Use it to standardize state management across small to medium React apps or to migrate from heavier solutions.

How this skill works

The skill generates concrete store templates and patterns: basic stores, middleware-wrapped stores (devtools, persist, immer), slice composition for modular state, and async action examples. It explains selector strategies (including shallow comparisons and computed selectors) and shows how to wire subscriptions and optimize component re-renders. All examples use TypeScript interfaces and StateCreator signatures so the output is ready to drop into a codebase.

When to use it

  • When you need a lightweight global state solution with strong TypeScript support.
  • When you want persistent client state (localStorage) with minimal setup.
  • When splitting large state into reusable slices for modular architecture.
  • When migrating from Redux or Context to a simpler, performant store.
  • When optimizing component re-renders with selectors and shallow comparisons.

Best practices

  • Define clear TypeScript interfaces for store shape and actions to keep type-safety end-to-end.
  • Use selectors and shallow comparisons to limit component re-renders.
  • Wrap complex nested updates with immer to keep code readable and immutable-friendly.
  • Persist only the minimal state subset (partialize) to avoid storing ephemeral data.
  • Split responsibilities into slices to improve testability and maintainability.

Example use cases

  • Cart store for e-commerce with add/remove/update, totals, and persistence across sessions.
  • User store with preferences persisted and DevTools for debugging authentication flows.
  • App store composed from auth and UI slices for a Next.js or T3-stack project.
  • Data fetching store with loading/error handling and an async fetch action.
  • Selector-driven components that read computed values like item counts to reduce renders.

FAQ

Can I combine multiple middleware like persist, devtools, and immer?

Yes. Compose middleware by wrapping the StateCreator in order (e.g., devtools(persist(immer(createSlice)))) and configure options like persist name and partialize.

How do slices help with scaling state?

Slices keep related state and actions colocated and let you merge them into one store. This improves organization, testability, and incremental refactors.

When should I prefer selectors over reading full state?

Use selectors when components only need specific fields or computed values. That minimizes re-renders and improves performance, especially with derived values.