From scaffolding
Guides state management decisions (local vs global), store structure, selectors, and re-render performance. Useful when creating stores or managing client-side state. Examples use Zustand.
How this skill is triggered — by the user, by Claude, or both
Slash command
/scaffolding:state-managementThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Client state management standards and best practices. The universal guidance
Client state management standards and best practices. The universal guidance (local-vs-global decisions, store structure, selectors, performance) applies to any store library; the concrete code examples use Zustand.
| Category | Solution | Use When |
|---|---|---|
| Local UI | useState | Single component, simple state |
| Shared UI | Zustand store | Multiple components need same data |
| Server data | React Query / SWR | API data with caching needs |
| Form state | useState / form library | Form inputs, validation |
| URL state | useSearchParams | Filter/sort params, shareable URLs |
| Element | Requirement |
|---|---|
| Interface | Define typed state + actions interface |
| State | Group related state together |
| Actions | Define all mutations as functions |
| Naming | use[Domain]Store convention |
| Principle | Description |
|---|---|
| Single responsibility | One store per domain (projects, UI, settings) |
| Flat structure | Avoid deeply nested state |
| Immutable updates | Always return new objects |
| Colocated actions | Keep actions inside store definition |
| Rule | Reason |
|---|---|
| Select minimal data | Reduces re-renders |
Use shallow for objects | Prevents unnecessary updates |
| Memoize derived data | Use useMemo for computed values |
| Avoid selecting entire store | Causes re-render on any change |
| Pattern | Use Case |
|---|---|
| Single value | (state) => state.count |
| Multiple values | (state) => ({ a: state.a, b: state.b }), shallow |
| Derived value | useMemo outside store |
| Middleware | Purpose | When to Use |
|---|---|---|
persist | LocalStorage persistence | User preferences, settings |
devtools | Redux DevTools integration | Development debugging |
immer | Immutable updates | Complex nested state |
subscribeWithSelector | Granular subscriptions | Performance optimization |
src/stores/
├── index.ts # Re-exports all stores
├── projectStore.ts # Domain-specific store
├── uiStore.ts # UI state (modals, panels)
└── settingsStore.ts # User preferences
| Store Type | Contains |
|---|---|
| Domain store | Business entities, selections |
| UI store | Modal states, panel visibility, loading |
| Settings store | User preferences, persisted config |
shallow equality for object selectionssubscribeWithSelector for side effectsshallow| Anti-Pattern | Problem | Solution |
|---|---|---|
| Giant store | Hard to maintain, performance issues | Split by domain |
| Computed in store | Stale data, extra complexity | Use useMemo |
| Prop drilling | Bypasses store benefits | Use store directly |
| Store in useState | Loses reactivity | Use store hook |
| No TypeScript | Runtime errors | Define interfaces |
| Scenario | Recommended Solution |
|---|---|
| Form input state | useState |
| Modal open/close | useState or UI store |
| Selected item (shared) | Domain store |
| User preferences | Persisted store |
| API response data | Store + service function |
| Computed/derived data | useMemo from store values |
| Global loading state | UI store |
Illustrative — these are one team's concrete Zustand conventions shown as an example. Substitute your store library's equivalents (Redux Toolkit, Jotai, Pinia, signals, etc.). The decision matrix and selector/performance guidance above are the reusable, library-agnostic part.
<your-stores-module>/)| Store | Persist | Purpose |
|---|---|---|
workspaceStore.ts | Yes (scaffolding-workspaces) | Projects/workspaces, active selection |
taskStore.ts | No | Task CRUD, SSE updates, agent statuses |
authStore.ts | Yes (auth-storage) | GitHub OAuth state, user profile |
Every store defines type XxxState and type XxxActions, then exports the combined type:
type WorkspaceState = {
workspaces: Workspace[];
activeWorkspace: Workspace | null;
};
type WorkspaceActions = {
addWorkspace: (pathOrWorkspace: string | Workspace) => void;
removeWorkspace: (path: string) => Promise<void>;
setActiveWorkspace: (path: string) => void;
loadFromApi: () => Promise<void>;
};
export type WorkspaceStore = WorkspaceState & WorkspaceActions;
partializePersisted stores use partialize to exclude actions and transient state from storage:
export const useWorkspaceStore = create<WorkspaceStore>()(
persist(
(set, get) => ({ /* state + actions */ }),
{
name: 'scaffolding-workspaces',
partialize: (state) => ({
workspaces: state.workspaces,
activeWorkspace: state.activeWorkspace,
}),
}
)
);
useShallow (<your-hooks-module>/)Stores are consumed through hook wrappers that use useShallow to prevent re-renders:
import { useWorkspaceStore } from '../stores/workspaceStore';
import { useShallow } from 'zustand/shallow';
export function useWorkspace() {
return useWorkspaceStore(
useShallow((state) => ({
workspaces: state.workspaces,
activeWorkspace: state.activeWorkspace,
addWorkspace: state.addWorkspace,
// ... selected fields only
}))
);
}
loadFromApi)Stores hydrate from localStorage first, then enrich from API:
persist middleware loads cached state from localStorage on mountloadFromApi() fetches from backend, merges fields (id, github_url, role) into existing entriesNon-persisted stores receive real-time updates from Server-Sent Events:
addTask(task) - deduplicates before prepending to listupdateTask(task) - replaces full task object in list and currentTaskupdateTaskPartial(id, updates) - merges partial fields from SSE completion eventsupdateAgentStatus(taskId, status) - tracks per-agent progress within a taskStores use extractErrorMessage(err, fallbackMsg) utility and store errors in state:
catch (err) {
const message = extractErrorMessage(err, 'Failed to fetch tasks');
set({ error: message, isLoading: false });
}
For expected errors (e.g., 409 conflict on cancel), check AxiosError status before setting error state.
npx claudepluginhub komluk/scaffolding --plugin scaffoldingGuides frontend state management in React: local/global decisions, Zustand/Redux Toolkit/Jotai/MobX/Context, TanStack Query/SWR for server state, optimistic updates, XState machines. Use for store setup, migrations, re-render fixes.
Provides Zustand 5.x patterns for React state management: slices, middleware, Immer, useShallow, persistence, selectors, devtools, async actions, and anti-patterns with TanStack Query integration. Use for global client state without boilerplate.
Implements React state management patterns with Redux Toolkit, Zustand, Jotai, React Query for global state, server state, optimistic updates, and library selection.