
LobeHub Zustand State Management
FreeStreamline your Zustand store management with clear conventions.
Free · Opens the source repo
What LobeHub Zustand State Management does
LobeHub Zustand State Management provides a structured approach to managing state in applications using Zustand. This skill is particularly useful for developers who want to implement best practices when creating and managing store slices, public and internal actions, and dispatch methods. It emphasizes naming conventions and patterns that help maintain clarity and consistency across your state management codebase.
The skill outlines a clear hierarchy for action types, distinguishing between public actions that interface with UI components and internal actions that handle core business logic. By following these conventions, developers can ensure that their actions are well-organized and easier to maintain. The skill also provides guidelines on when to use reducers versus simple state updates, which can significantly affect the performance and reliability of state transitions in your application.
In addition, the skill introduces the optimistic update pattern, which allows for immediate UI feedback while waiting for backend operations to complete. This pattern is crucial for enhancing user experience, especially in applications that require real-time interactions. The documentation includes practical examples and code snippets that illustrate how to implement these concepts effectively.
For those migrating from plain StateCreator objects to class-based actions, this skill offers a comprehensive guide on best practices. It covers how to encapsulate actions within classes, manage dependencies between actions, and utilize helper types for better type safety. Overall, this skill is designed for developers looking to improve their Zustand state management practices and create more maintainable and efficient applications.
When to use it
Use this skill when developing applications with Zustand to ensure consistent and maintainable state management practices.
When not to use it
This skill may not be suitable for projects that do not use Zustand for state management or for developers who prefer a less structured approach.
What you can build with it
Implementing Optimistic Updates
Utilize the optimistic update pattern to provide immediate feedback in your UI when creating new topics, enhancing user experience.
Migrating to Class-Based Actions
Follow the guidelines to transition from plain StateCreator objects to class-based actions, improving encapsulation and maintainability.
Organizing State Management Code
Adopt the naming conventions and action type hierarchy to keep your Zustand store organized and easy to navigate.
How to install LobeHub Zustand State Management
View source1. Install with the skills CLI
npx skills add lobehub/lobehub/zustand --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by lobehubLobeHub 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_dispatchTopicState: -
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
#privatefields (e.g.,#set,#get) to avoid leaking internals. - Prefer shared typing helpers:
StoreSetter<T>from@/store/typesforset.Pick<ActionImpl, keyof ActionImpl>to expose only public methods.
- Export a
create*Slicehelper 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). flattenActionsbinds 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:
ChatGroupStoreWithSwitchTopicfor lifecycleswitchTopicChatGroupStoreWithRefreshfor member refreshChatGroupStoreWithInternalfor curdinternal_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
#setwhen unused; restore it when a later edit needsset— re-adding costs nothing. - Don't add
setNamespacefor slices that don't write state. - Don't keep both old slice objects and class actions active at the same time during migration.
Frequently asked questions about LobeHub Zustand State Management
Similar skills
Spring Boot Testing
Master testing techniques for Spring Boot 4 applications.
GitHub Issues
Manage GitHub issues efficiently with MCP tools.
Geofeed Tuner
Optimize your IP geolocation feeds in CSV format.
Batch Files
Master Windows batch scripting for automation and task management.
Adobe Illustrator Scripting
Automate your Illustrator workflows with ExtendScript.
Plugin Structure
Create and organize Claude Code plugins effectively.
