Back to TypeScript

LobeHub Zustand Store Patterns

zustandtypescriptstate-managementarchitecturelobelhub
β˜… 4.7 (37)⭐ 78.7kπŸ“„ NOASSERTIONπŸ•’ 2026-06-16Source β†—

Install this skill

npx skills add lobehub/lobehub

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

What this skill does

  • β€’Class-based action encapsulation using private fields
  • β€’Strict separation between UI public actions and internal state dispatchers
  • β€’Helper utilities for flattening multi-class slices into a single store
  • β€’Optimistic update patterns for immediate UI responsiveness
  • β€’Strong typing support for complex state slices and action composition

When to use it

  • βœ“Refactoring large, unmanageable Zustand slices into modular classes
  • βœ“Implementing complex features requiring multiple service calls and optimistic UI updates
  • βœ“Standardizing state management across a multi-agent application team
  • βœ“Building high-performance chat features needing granular state access

When not to use it

  • βœ•Simple state requirements that only involve toggling boolean flags
  • βœ•Small projects where plain function-based Zustand stores are sufficient

How to invoke it

Example prompts that trigger this skill:

  • β€œImplement a new class-based action slice for the store following LobeHub patterns”
  • β€œConvert legacy object-based slice to a class-based structure”
  • β€œCreate a new internal dispatch method for a specific state entity”
  • β€œAdd an optimistic update flow within an existing internal action method”
  • β€œCompose multiple action classes into a single store using flattenActions”

Example workflow

  1. Create a new class for the feature actions that accepts set and get in the constructor
  2. Define internal logic methods prefixed with 'internal_' for data handling
  3. Expose public action methods as class methods for UI consumption
  4. Define a slice creator function to instantiate the class with store parameters
  5. Merge the slice into the main store using the flattenActions helper

Prerequisites

  • –Zustand
  • –TypeScript
  • –Familiarity with functional programming patterns in React

Pitfalls & limitations

  • !Forgetting to use private class fields can leak internal store logic
  • !Over-using optimistic updates for operations that require high data integrity
  • !Complex slice compositions require careful management of store-access types

FAQ

Why use class-based actions over standard functions?
Class-based actions provide better encapsulation through private fields and clearer organization for complex features compared to sprawling object literals.
What is the purpose of 'internal_dispatch' methods?
These methods strictly separate state mutations from business logic, ensuring that UI components only trigger updates through established, validated pathways.
When should I use simple 'set' versus a reducer pattern?
Use simple 'set' for atomic, primitive state changes like booleans. Use the reducer pattern for managing complex data structures like maps and object arrays.

How it compares

Unlike standard Zustand usage where logic is often colocated in one file, this pattern mandates a rigid architecture that scales better for large teams by strictly isolating the UI interface from core business logic.

Source & trust

⭐ 79k starsπŸ“„ NOASSERTIONπŸ•’ Updated 2026-06-16πŸ›‘ no risky patterns found

From the source: β€œ# LobeHub 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 implementati…”

View the full SKILL.md source

# LobeHub 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`
  **State:**

- ID arrays: `topicEditingIds`

- Maps: `topicMaps`, `messagesMap`

- Active: `activeTopicId`

- Init flags: `topicsInit`

## Detailed Guides

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

## Class-Based Action Implementation

We are migrating slices from plain `StateCreator` objects to **class-based actions**.

### Pattern

- Define a class that encapsulates actions and receives `(set, get, api)` in the constructor.
- Use `#private` fields (e.g., `#set`, `#get`) to avoid leaking internals.
- Prefer shared typing helpers:
  - `StoreSetter<T>` from `@/store/types` for `set`.
  - `Pick<ActionImpl, keyof ActionImpl>` to expose only public methods.
- Export a `create*Slice` helper that returns a class instance.

```ts
type Setter = StoreSetter<HomeStore>;
export const createRecentSlice = (set: Setter, get: () => HomeStore, _api?: unknown) =>
  new RecentActionImpl(set, get, _api);

export class RecentActionImpl {
  readonly #get: () => HomeStore;
  readonly #set: Setter;

  constructor(set: Setter, get: () => HomeStore, _api?: unknown) {
    void _api;
    this.#set = set;
    this.#get = get;
  }

  useFetchRecentTopics = () => {
    // ...
  };
}

export type RecentAction = Pick<RecentActionImpl, keyof RecentActionImpl>;
```

### Composition

- In store files, merge class instances with `flattenActions` (do not spread class instances).
- `flattenActions` binds methods to the original class instance and supports prototype methods and class fields.

```ts
const createStore: StateCreator<HomeStore, [['zustand/devtools', never]]> = (...params) => ({
  ...initialState,
  ...flattenActions<HomeStoreAction>([
    createRecentSlice(...params),
    createHomeInputSlice(...params),
  ]),
});
```

### Multi-Class Slices

- For large slices that need multiple action classes, compose them in the slice entry using `flattenActions`.
- Use a local `PublicActions<T>` helper if you need to combine multiple classes and hide private fields.

```ts
type PublicActions<T> = { [K in keyof T]: T[K] };

export type ChatGroupAction = PublicActions<
  ChatGroupInternalAction & ChatGroupLifecycleAction & ChatGroupMemberAction & ChatGroupCurdAction
>;

export const chatGroupAction: StateCreator<
  ChatGroupStore,
  [['zustand/devtools', never]],
  [],
  ChatGroupAction
> = (...params) =>
  flattenActions<ChatGroupAction>([
    new ChatGroupInternalAction(...params),
    new ChatGroupLifecycleAction(...params),
    new ChatGroupMemberAction(...params),
    new ChatGroupCurdAction(...params),
  ]);
```

### Store-Access Types

- For class methods that depend on actions in other classes, define explicit store augmentations:
  - `ChatGroupStoreWithSwitchTopic` for lifecycle `switchTopic`
  - `ChatGroupStoreWithRefresh` for member refresh
  - `ChatGroupStoreWithInternal` for curd `internal_dispatchChatGroup`

### Slices That Don't Currently Need `set`

When a slice doesn't write local state (e.g. it delegates to another store or just runs hooks), drop `#set` and mark the constructor param as `_set` with `void _set` to keep the `(set, get, api)` shape:

```ts
export class ToolActionImpl {
  readonly #get: () => ConversationStore;

  constructor(_set: Setter, get: () => ConversationStore, _api?: unknown) {
    void _set;
    void _api;
    this.#get = get;
  }

  approveToolCall = async (id: string) => {
    const { context, hooks } = this.#get();
    await useChatStore.getState().approveToolCalling(id, '', context);
    hooks.onToolCallComplete?.(id, undefined);
  };
}
```

- Drop `#set` when unused; restore it when a later edit needs `set` β€” re-adding costs nothing.
- Don't add `setNamespace` for slices that don't write state.
- Don't keep both old slice objects and class actions active at the same time during migration.

Quoted from lobehub/lobehub for reference β€” see the original for the authoritative, latest version.

πŸ“„ Full skill instructions β€” original source: lobehub/lobehub
The LobeHub Zustand state management convention provides a structured approach for managing complex, agent-based AI application state. It moves away from monolithic state objects, favoring modular class-based action implementation and strict separation of concerns. Developers benefit from a clear hierarchy: public UI actions for orchestration, internal actions for business logic and state updates, and dispatch methods for safe store mutations. By organizing slices into class instances and composing them with flattened action patterns, this methodology prevents common state-related bugs in high-frequency, asynchronous environments like chat applications. It offers a consistent architectural blueprint for scaling shared store logic, ensuring that optimistic updates, service calls, and data mapping remain maintainable even as the feature set expands.

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/zustand/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/lobehub/lobehub/zustand/SKILL.md
  • Cursor: ~/.cursor/skills/lobehub/lobehub/zustand/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/lobehub/lobehub/zustand/SKILL.md

πŸš€ Install with CLI:
npx skills add lobehub/lobehub

Read the Master Guide: Mastering Agent Skills β†’

Recommended Rules

View more rules β†’

Recommended Workflows

View more workflows β†’

Recommended MCP Servers

View more MCP servers β†’

Take It Further

Maximize your productivity with these powerful resources

πŸ“‹

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
πŸ“–

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid typescript issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under TypeScript and is published by lobehub, maintained in lobehub/lobehub.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.