Back to skills
SkillHub ClubShip Full StackFull Stack

zustand

Zustand state management guide. Use when working with store code (src/store/**), implementing actions, managing state, or creating slices. Triggers on Zustand store development, state management questions, or action implementation.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
73,597
Hot score
99
Updated
March 19, 2026
Overall rating
C5.3
Composite score
5.3
Best-practice grade
A88.4

Install command

npx @skill-hub/cli install lobehub-lobehub-zustand

Repository

lobehub/lobehub

Skill path: .agents/skills/zustand

Zustand state management guide. Use when working with store code (src/store/**), implementing actions, managing state, or creating slices. Triggers on Zustand store development, state management questions, or action implementation.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: lobehub.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install zustand into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/lobehub/lobehub before adding zustand to shared team environments
  • Use zustand for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: zustand
description: Zustand state management guide. Use when working with store code (src/store/**), implementing actions, managing state, or creating slices. Triggers on Zustand store development, state management questions, or action implementation.
---

# LobeChat Zustand State Management

## Action Type Hierarchy

### 1. Public Actions
Main interfaces for UI components:
- Naming: Verb form (`createTopic`, `sendMessage`)
- Responsibilities: Parameter validation, flow orchestration

### 2. Internal Actions (`internal_*`)
Core business logic implementation:
- Naming: `internal_` prefix (`internal_createTopic`)
- Responsibilities: Optimistic updates, service calls, error handling
- Should not be called directly by UI

### 3. Dispatch Methods (`internal_dispatch*`)
State update handlers:
- Naming: `internal_dispatch` + entity (`internal_dispatchTopic`)
- Responsibilities: Calling reducers, updating store

## When to Use Reducer vs Simple `set`

**Use Reducer Pattern:**
- Managing object lists/maps (`messagesMap`, `topicMaps`)
- Optimistic updates
- Complex state transitions

**Use Simple `set`:**
- Toggling booleans
- Updating simple values
- Setting single state fields

## Optimistic Update Pattern

```typescript
internal_createTopic: async (params) => {
  const tmpId = Date.now().toString();

  // 1. Immediately update frontend (optimistic)
  get().internal_dispatchTopic(
    { type: 'addTopic', value: { ...params, id: tmpId } },
    'internal_createTopic'
  );

  // 2. Call backend service
  const topicId = await topicService.createTopic(params);

  // 3. Refresh for consistency
  await get().refreshTopic();
  return topicId;
},
```

**Delete operations**: Don't use optimistic updates (destructive, complex recovery)

## Naming Conventions

**Actions:**
- Public: `createTopic`, `sendMessage`
- Internal: `internal_createTopic`, `internal_updateMessageContent`
- Dispatch: `internal_dispatchTopic`
- Toggle: `internal_toggleMessageLoading`

**State:**
- ID arrays: `messageLoadingIds`, `topicEditingIds`
- Maps: `topicMaps`, `messagesMap`
- Active: `activeTopicId`
- Init flags: `topicsInit`

## Detailed Guides

- Action patterns: `references/action-patterns.md`
- Slice organization: `references/slice-organization.md`


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/action-patterns.md

```markdown
# Zustand Action Patterns

## Optimistic Update Implementation

### Standard Flow

```typescript
internal_updateMessageContent: async (id, content, extra) => {
  const { internal_dispatchMessage, refreshMessages } = get();

  // 1. Immediately update frontend
  internal_dispatchMessage({
    id,
    type: 'updateMessage',
    value: { content },
  });

  // 2. Call backend
  await messageService.updateMessage(id, { content });

  // 3. Refresh for consistency
  await refreshMessages();
},
```

### Create Operations

```typescript
internal_createMessage: async (message, context) => {
  let tempId = context?.tempMessageId;
  if (!tempId) {
    tempId = internal_createTmpMessage(message);
    internal_toggleMessageLoading(true, tempId);
  }

  try {
    const id = await messageService.createMessage(message);
    await refreshMessages();
    internal_toggleMessageLoading(false, tempId);
    return id;
  } catch (e) {
    internal_toggleMessageLoading(false, tempId);
    internal_dispatchMessage({
      id: tempId,
      type: 'updateMessage',
      value: { error: { type: ChatErrorType.CreateMessageError } },
    });
  }
},
```

### Delete Operations (No Optimistic Update)

```typescript
internal_removeGenerationTopic: async (id: string) => {
  get().internal_updateGenerationTopicLoading(id, true);

  try {
    await generationTopicService.deleteTopic(id);
    await get().refreshGenerationTopics();
  } finally {
    get().internal_updateGenerationTopicLoading(id, false);
  }
},
```

## Loading State Management

```typescript
// Define in initialState.ts
export interface ChatMessageState {
  messageEditingIds: string[];
}

// Manage in action
toggleMessageEditing: (id, editing) => {
  set(
    { messageEditingIds: toggleBooleanList(get().messageEditingIds, id, editing) },
    false,
    'toggleMessageEditing'
  );
}
```

## SWR Integration

```typescript
useFetchMessages: (enable, sessionId, activeTopicId) =>
  useClientDataSWR<ChatMessage[]>(
    enable ? [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId] : null,
    async ([, sessionId, topicId]) => messageService.getMessages(sessionId, topicId),
    {
      onSuccess: (messages) => {
        const nextMap = { ...get().messagesMap, [messageMapKey(sessionId, activeTopicId)]: messages };
        if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;
        set({ messagesInit: true, messagesMap: nextMap }, false, n('useFetchMessages'));
      },
    }
  ),

// Cache invalidation
refreshMessages: async () => {
  await mutate([SWR_USE_FETCH_MESSAGES, get().activeId, get().activeTopicId]);
};
```

## Reducer Pattern

```typescript
export const messagesReducer = (state: ChatMessage[], payload: MessageDispatch): ChatMessage[] => {
  switch (payload.type) {
    case 'updateMessage': {
      return produce(state, (draftState) => {
        const index = draftState.findIndex((i) => i.id === payload.id);
        if (index < 0) return;
        draftState[index] = merge(draftState[index], {
          ...payload.value,
          updatedAt: Date.now(),
        });
      });
    }
    // ...other cases
  }
};
```

```

### references/slice-organization.md

```markdown
# Zustand Slice Organization

## Top-Level Store Structure

Key aggregation files:
- `src/store/chat/initialState.ts`: Aggregate all slice initial states
- `src/store/chat/store.ts`: Define top-level `ChatStore`, combine all slice actions
- `src/store/chat/selectors.ts`: Export all slice selectors
- `src/store/chat/helpers.ts`: Chat helper functions

## Store Aggregation Pattern

```typescript
// src/store/chat/initialState.ts
import { ChatTopicState, initialTopicState } from './slices/topic/initialState';
import { ChatMessageState, initialMessageState } from './slices/message/initialState';

export type ChatStoreState = ChatTopicState & ChatMessageState & ...

export const initialState: ChatStoreState = {
  ...initialMessageState,
  ...initialTopicState,
  ...
};

// src/store/chat/store.ts
export interface ChatStoreAction
  extends ChatMessageAction, ChatTopicAction, ...

const createStore: StateCreator<ChatStore, [['zustand/devtools', never]]> = (...params) => ({
  ...initialState,
  ...chatMessage(...params),
  ...chatTopic(...params),
});

export const useChatStore = createWithEqualityFn<ChatStore>()(
  subscribeWithSelector(devtools(createStore)),
  shallow
);
```

## Single Slice Structure

```plaintext
src/store/chat/slices/
└── [sliceName]/
    ├── action.ts          # Define actions (or actions/ directory)
    ├── initialState.ts    # State structure and initial values
    ├── reducer.ts         # (Optional) Reducer pattern
    ├── selectors.ts       # Define selectors
    └── index.ts           # (Optional) Re-exports
```

### initialState.ts

```typescript
export interface ChatTopicState {
  activeTopicId?: string;
  topicMaps: Record<string, ChatTopic[]>;
  topicsInit: boolean;
  topicLoadingIds: string[];
}

export const initialTopicState: ChatTopicState = {
  activeTopicId: undefined,
  topicMaps: {},
  topicsInit: false,
  topicLoadingIds: [],
};
```

### selectors.ts

```typescript
const currentTopics = (s: ChatStoreState): ChatTopic[] | undefined => s.topicMaps[s.activeId];

const getTopicById = (id: string) => (s: ChatStoreState): ChatTopic | undefined =>
  currentTopics(s)?.find((topic) => topic.id === id);

// Core pattern: Use xxxSelectors aggregate
export const topicSelectors = {
  currentTopics,
  getTopicById,
};
```

## Complex Actions Sub-directory

```plaintext
src/store/chat/slices/aiChat/
├── actions/
│   ├── generateAIChat.ts
│   ├── rag.ts
│   ├── memory.ts
│   └── index.ts
├── initialState.ts
└── selectors.ts
```

## State Design Patterns

### Map Structure for Associated Data
```typescript
topicMaps: Record<string, ChatTopic[]>;
messagesMap: Record<string, ChatMessage[]>;
```

### Arrays for Loading State
```typescript
messageLoadingIds: string[]
topicLoadingIds: string[]
```

### Optional Fields for Active Items
```typescript
activeId: string
activeTopicId?: string
```

## Best Practices

1. **Slice division**: By functional domain (message, topic, aiChat)
2. **File naming**: camelCase for directories, consistent patterns
3. **State structure**: Flat, avoid deep nesting
4. **Type safety**: Clear TypeScript interfaces for each slice

```

zustand | SkillHub