# SkillPatch skill: zustand-store-ts

This skill guides agents in creating Zustand stores with TypeScript, including proper use of the `subscribeWithSelector` middleware, state/action interface separation, and selector patterns. It provides code templates, best practices, and integration steps for React global state management. Ideal for scaffolding or reviewing Zustand-based state management in TypeScript React projects.

You (the agent) were given this URL and asked to use this skill. This is a **public** skill — no SkillPatch account, API key, or prior setup is required. Two ways to use it:

**1. Use it right now** — the skill's complete file tree (SKILL.md + all reference files) is inlined below; read `SKILL.md` first, then follow it, consulting the other files as it directs.

**2. Install the exact package onto disk** (recommended if you can run a shell — this reproduces the skill byte-for-byte, including any binary assets that can't be inlined):

```bash
mkdir -p .claude/skills/zustand-store-ts
curl -sSL https://skillpatch.dev/install_skill/zustand-store-ts | tar -xz -C .claude/skills/
```

(`.claude/skills/` is Claude Code's convention; use whatever directory your agent loads skills from.)


---

## Skill files (2)

- `SKILL.md`
- `assets/template.ts`


### `SKILL.md`

````markdown
---
name: zustand-store-ts
description: Create Zustand stores with TypeScript, subscribeWithSelector middleware, and proper state/action separation. Use when building React state management, creating global stores, or implementing reactive state patterns with Zustand.
license: MIT
metadata:
  author: Microsoft
  version: "1.0.0"
---

# Zustand Store

Create Zustand stores following established patterns with proper TypeScript types and middleware.

## Quick Start

Copy the template from [assets/template.ts](assets/template.ts) and replace placeholders:
- `{{StoreName}}` → PascalCase store name (e.g., `Project`)
- `{{description}}` → Brief description for JSDoc

## Always Use subscribeWithSelector

```typescript
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';

export const useMyStore = create<MyStore>()(
  subscribeWithSelector((set, get) => ({
    // state and actions
  }))
);
```

## Separate State and Actions

```typescript
export interface MyState {
  items: Item[];
  isLoading: boolean;
}

export interface MyActions {
  addItem: (item: Item) => void;
  loadItems: () => Promise<void>;
}

export type MyStore = MyState & MyActions;
```

## Use Individual Selectors

```typescript
// Good - only re-renders when `items` changes
const items = useMyStore((state) => state.items);

// Avoid - re-renders on any state change
const { items, isLoading } = useMyStore();
```

## Subscribe Outside React

```typescript
useMyStore.subscribe(
  (state) => state.selectedId,
  (selectedId) => console.log('Selected:', selectedId)
);
```

## Integration Steps

1. Create store in `src/frontend/src/store/`
2. Export from `src/frontend/src/store/index.ts`
3. Add tests in `src/frontend/src/store/*.test.ts`

````


### `assets/template.ts`

````
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';

// ============================================================================
// Types
// ============================================================================

/**
 * {{StoreName}}State - The state shape for this store
 */
export interface {{StoreName}}State {
  // Add state properties
  items: unknown[];
  selectedId: string | null;
  isLoading: boolean;
  error: string | null;
}

/**
 * {{StoreName}}Actions - Actions that can be performed on this store
 */
export interface {{StoreName}}Actions {
  // Setters
  setItems: (items: unknown[]) => void;
  setSelectedId: (id: string | null) => void;
  setLoading: (loading: boolean) => void;
  setError: (error: string | null) => void;

  // Complex actions
  loadItems: () => Promise<void>;
  addItem: (item: unknown) => void;
  removeItem: (id: string) => void;

  // Reset
  reset: () => void;
}

/**
 * {{StoreName}}Store - Combined store type
 */
export type {{StoreName}}Store = {{StoreName}}State & {{StoreName}}Actions;

// ============================================================================
// Initial State
// ============================================================================

const initialState: {{StoreName}}State = {
  items: [],
  selectedId: null,
  isLoading: false,
  error: null,
};

// ============================================================================
// Store
// ============================================================================

/**
 * use{{StoreName}}Store - Zustand store for managing {{description}}
 *
 * @example
 * ```typescript
 * // In a component - use individual selectors for performance
 * const items = use{{StoreName}}Store((state) => state.items);
 * const loadItems = use{{StoreName}}Store((state) => state.loadItems);
 *
 * // Subscribe to changes outside React
 * use{{StoreName}}Store.subscribe(
 *   (state) => state.selectedId,
 *   (selectedId) => console.log('Selected:', selectedId)
 * );
 * ```
 */
export const use{{StoreName}}Store = create<{{StoreName}}Store>()(
  subscribeWithSelector((set, get) => ({
    // Initial state
    ...initialState,

    // Simple setters
    setItems: (items) => set({ items }),
    setSelectedId: (selectedId) => set({ selectedId }),
    setLoading: (isLoading) => set({ isLoading }),
    setError: (error) => set({ error }),

    // Async action example
    loadItems: async () => {
      set({ isLoading: true, error: null });
      try {
        // const items = await fetchItems();
        const items: unknown[] = []; // Replace with actual fetch
        set({ items, isLoading: false });
      } catch (error) {
        set({
          error: error instanceof Error ? error.message : 'Failed to load',
          isLoading: false,
        });
      }
    },

    // Add item (immutable update)
    addItem: (item) => {
      set({ items: [...get().items, item] });
    },

    // Remove item (immutable update)
    removeItem: (id) => {
      set({
        items: get().items.filter((item) => (item as { id: string }).id !== id),
        // Clear selection if removed item was selected
        selectedId: get().selectedId === id ? null : get().selectedId,
      });
    },

    // Reset to initial state
    reset: () => set(initialState),
  }))
);

````
