zustand
Original:🇺🇸 English
Not Translated
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.
2installs
Sourcelobehub/lobe-chat
Added on
NPX Install
npx skill4agent add lobehub/lobe-chat zustandSKILL.md Content
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_*
)
internal_*Core business logic implementation:
- Naming: prefix (
internal_)internal_createTopic - Responsibilities: Optimistic updates, service calls, error handling
- Should not be called directly by UI
3. Dispatch Methods (internal_dispatch*
)
internal_dispatch*State update handlers:
- Naming: + entity (
internal_dispatch)internal_dispatchTopic - Responsibilities: Calling reducers, updating store
When to Use Reducer vs Simple set
setUse 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: ,
createTopicsendMessage - Internal: ,
internal_createTopicinternal_updateMessageContent - Dispatch:
internal_dispatchTopic - Toggle:
internal_toggleMessageLoading
State:
- ID arrays: ,
messageLoadingIdstopicEditingIds - Maps: ,
topicMapsmessagesMap - 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 objects to class-based actions.
StateCreatorPattern
- Define a class that encapsulates actions and receives in the constructor.
(set, get, api) - Use fields (e.g.,
#private,#set) to avoid leaking internals.#get - Prefer shared typing helpers:
- from
StoreSetter<T>for@/store/types.set - to expose only public methods.
Pick<ActionImpl, keyof ActionImpl>
- Export a helper that returns a class instance.
create*Slice
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 (do not spread class instances).
flattenActions - binds methods to the original class instance and supports prototype methods and class fields.
flattenActions
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 helper if you need to combine multiple classes and hide private fields.
PublicActions<T>
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:
- for lifecycle
ChatGroupStoreWithSwitchTopicswitchTopic - for member refresh
ChatGroupStoreWithRefresh - for curd
ChatGroupStoreWithInternalinternal_dispatchChatGroup
Do / Don't
- Do: keep constructor signature aligned with params
StateCreator.(set, get, api) - Do: use to avoid
#privatebeing exposed.set/get - Do: use instead of spreading class instances.
flattenActions - Don't: keep both old slice objects and class actions active at the same time.