Skip to content
Back to skills

Zustand

ASecurity

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.

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 27, 2026
developmenttypescriptreactapifrontendbackend

Works with

  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned May 27, 2026

npx -y skills add ByeongminLee/nextjs-claude-code --skill zustand --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Zustand?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Zustand
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/byeongminlee-zustand/badge)](https://www.skillsdirectory.com/skills/byeongminlee-zustand)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
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.
---

# 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`
- 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`

## 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`

### Do / Don't

- **Do**: keep constructor signature aligned with `StateCreator` params `(set, get, api)`.
- **Do**: use `#private` to avoid `set/get` being exposed.
- **Do**: use `flattenActions` instead of spreading class instances.
- **Don't**: keep both old slice objects and class actions active at the same time.

Files in this skill

  • SKILL.md5.1 KB
  • references/action-patterns.md3 KB
  • references/slice-organization.md3.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.