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.
Main interfaces for UI components:
createTopic, sendMessage)internal_*)Core business logic implementation:
internal_ prefix (internal_createTopic)internal_dispatch*)State update handlers:
internal_dispatch + entity (internal_dispatchTopic)setUse Reducer Pattern:
messagesMap, topicMaps)Use Simple set:
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)
Actions:
Public: createTopic, sendMessage
Internal: internal_createTopic, internal_updateMessageContent
Dispatch: internal_dispatchTopic
State:
ID arrays: topicEditingIds
Maps: topicMaps, messagesMap
Active: activeTopicId
Init flags: topicsInit
references/action-patterns.mdreferences/slice-organization.mdWe are migrating slices from plain StateCreator objects to class-based actions.
(set, get, api) in the constructor.#private fields (e.g., #set, #get) to avoid leaking internals.StoreSetter<T> from @/store/types for set.Pick<ActionImpl, keyof ActionImpl> to expose only public methods.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>;
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),
]),
});
flattenActions.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),
]);
ChatGroupStoreWithSwitchTopic for lifecycle switchTopicChatGroupStoreWithRefresh for member refreshChatGroupStoreWithInternal for curd internal_dispatchChatGroupStateCreator params (set, get, api).#private to avoid set/get being exposed.flattenActions instead of spreading class instances.