Provides architectural patterns and best practices for implementing Zustand stores in React applications.

Install

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

Installs to .claude/skills/zustand

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.

LobeHub Zustand store conventions. Use when editing src/store, store slices, public/internal actions, dispatch actions, flattenActions, optimistic updates, selectors, maps, or class action migration.
199 chars✓ has a “when” trigger
Intermediate

Key capabilities

  • Differentiate between public and internal action logic
  • Implement optimistic update patterns for UI responsiveness
  • Organize state into maps and ID arrays for lookup efficiency
  • Decouple state dispatchers from business logic functions

How it works

Applies a structured hierarchy of public actions, internal logic functions, and dispatchers to maintain separation of concerns within a store.

Inputs & outputs

You give it
State slice definitions and business action requirements
You get back
Zustand store implementation code with typed actions

When to use zustand

  • Implement complex state slices
  • Apply optimistic update patterns
  • Migrate class-based actions to store functions
  • Define internal versus public dispatch methods

About this skill

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

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.
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.
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.
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:

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.

When not to use it

  • Managing simple local component state (use React.useState)
  • High-frequency numerical computation outside of state updates

Prerequisites

Zustand libraryTypeScript environment

Limitations

  • Strict naming conventions for internal vs public methods
  • Requires manual state normalization for complex objects

How it compares

It follows specific LobeHub conventions for naming and action structure rather than generic state management patterns.

Compared to similar skills

zustand side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
zustand (this skill)1132moNo flagsIntermediate
react342moNo flagsIntermediate
react-component-patterns31moNo flagsIntermediate
zustand-587moNo flagsBeginner

Try saying

Example prompts that trigger this skill in your AI assistant.

drizzle

lobehub

Drizzle ORM schema and database guide. Use when working with database schemas (src/database/schemas/*), defining tables, creating migrations, or database model code. Triggers on Drizzle schema definition, database migrations, or ORM usage questions.

238873

react

lobehub

React component development guide. Use when working with React components (.tsx files), creating UI, using @lobehub/ui components, implementing routing, or building frontend features. Triggers on React component creation, modification, layout implementation, or navigation tasks.

3480

typescript

lobehub

TypeScript code style and optimization guidelines. Use when writing TypeScript code (.ts, .tsx, .mts files), reviewing code quality, or implementing type-safe patterns. Triggers on TypeScript development, type safety questions, or code style discussions.

2877

project-overview

lobehub

Complete project architecture and structure guide. Use when exploring the codebase, understanding project organization, finding files, or needing comprehensive architectural context. Triggers on architecture questions, directory navigation, or project overview needs.

1548

linear

lobehub

Linear issue management guide. Use when working with Linear issues, creating issues, updating status, or adding comments. Triggers on Linear issue references (LOBE-xxx), issue tracking, or project management tasks. Requires Linear MCP tools to be available.

10117

desktop

lobehub

Electron desktop development guide. Use when implementing desktop features, IPC handlers, controllers, preload scripts, window management, menu configuration, or Electron-specific functionality. Triggers on desktop app development, Electron IPC, or desktop local tools implementation.

941

Search skills

Search the agent skills registry