
Superpower Zustand
- 4 installs
- 5 repo stars
- Updated February 7, 2026
- cygnusfear/claude-stuff
Enforces a standardized StoreBuilder pattern for creating Zustand stores with immer middleware, factory-pattern separation, and optional persistence.
About
This skill mandates a type-safe StoreBuilder pattern for all Zustand stores, separating state from actions and integrating immer middleware. A developer uses it whenever creating Zustand state stores to keep patterns consistent.
- Factory-pattern separation of state and actions
- Immer middleware plus optional fine-grained persistence
Superpower Zustand by the numbers
- 4 all-time installs (skills.sh)
- Ranked #1,810 of 2,244 Frontend Development skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cygnusfear/claude-stuff --skill superpower-zustandAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 5 |
| Last updated | February 7, 2026 |
| Repository | cygnusfear/claude-stuff ↗ |
What it does
Enforces a standardized StoreBuilder pattern for creating Zustand stores with immer middleware, factory-pattern separation, and optional persistence.
Files
Zustand StoreBuilder Pattern
<CRITICAL> DO NOT create Zustand stores using standard patterns (create with inline actions). ALL Zustand stores in this project MUST use the StoreBuilder pattern defined below. This is a required architectural standard, not a suggestion. </CRITICAL>
Purpose
Enforce a standardized, type-safe approach to creating Zustand stores that:
- Separates state definition from actions using the factory pattern
- Integrates immer middleware for convenient immutable updates
- Supports optional persistence with fine-grained control
- Exposes both reactive (useStore hook) and non-reactive (get/set) access
- Maintains consistent patterns across the codebase
When to Use This Skill
Use this skill when:
- Creating new Zustand stores for state management
- User requests state management solutions in a React application
- Implementing stores for any feature requiring client-side state
Required Pattern
All Zustand stores MUST use the StoreBuilder utility located in assets/storebuilder.ts.
Core Implementation Steps
1. Copy the StoreBuilder utility (if not already in the project)
- Source:
skills/superpower-zustand/assets/storebuilder.ts - Destination:
src/lib/storebuilder.ts(or similar location in the project)
2. Define state type separately from actions
- Create a type for the full store (state + actions)
- Use
Omitto exclude action methods when passing to StoreBuilder
3. Initialize the store with StoreBuilder
- Pass initial state as first argument
- Optionally pass PersistConfig as second argument for persistence
4. Separate actions using createFactory
- Define all actions as methods in the createFactory argument
- Actions access
setfrom the StoreBuilder closure - Use immer-style mutations within
setcallbacks
5. Export the factory-created hook
- The hook returned by createFactory combines state, actions, and store utilities
Required Code Structure
import { StoreBuilder } from './storebuilder';
// 1. Define complete state type
type MyStoreState = {
// State fields
value: number;
items: string[];
// Action methods
setValue: (v: number) => void;
addItem: (item: string) => void;
};
// 2. Initialize StoreBuilder with state only (Omit actions)
const { set, createFactory } = StoreBuilder<Omit<MyStoreState, 'setValue' | 'addItem'>>(
{
value: 0,
items: [],
},
// Optional: persistence config
// {
// name: 'my-store',
// version: 1,
// }
);
// 3. Create factory with actions
const useMyStore = createFactory({
setValue: (v: number) => set((state) => { state.value = v; }),
addItem: (item: string) => set((state) => { state.items.push(item); }),
});
// 4. Export the hook
export { useMyStore };State Updates with Immer
When using set, write mutations directly on the draft state (immer middleware is included):
// ✅ Correct: Mutate draft
set((state) => {
state.count += 1;
state.items.push(newItem);
state.nested.property = 'value';
});
// ❌ Incorrect: Don't return new object
set((state) => ({ ...state, count: state.count + 1 }));Persistence Configuration
When state should persist across sessions:
const { createFactory } = StoreBuilder(
initialState,
{
name: 'storage-key', // Required: localStorage key
version: 1, // Optional: for migration handling
storage: sessionStorage, // Optional: defaults to localStorage
partialize: (state) => ({ // Optional: persist only specific fields
theme: state.theme,
preferences: state.preferences,
}),
}
);Reference Documentation
For detailed examples and advanced patterns, read references/pattern-guide.md:
- Basic usage examples
- Persistence patterns
- Complex stores with async actions
- Using get/set outside React components
- Type safety patterns
Load the reference documentation when:
- Implementing complex stores with async operations
- Needing examples of persistence configuration
- User asks about advanced Zustand patterns
- Unsure about specific implementation details
Verification
After creating a store, verify: 1. ✅ StoreBuilder utility is imported from project location 2. ✅ State type uses Omit to exclude actions 3. ✅ All actions are defined in createFactory, not in initial state 4. ✅ State updates use immer-style mutations (mutate draft, don't return new object) 5. ✅ Exported hook name follows convention (e.g., useMyStore) 6. ✅ Persistence config is included if state should persist
Non-React Usage
The pattern supports non-reactive access outside React components:
const { get, set, subscribe } = StoreBuilder(initialState);
// Get current state
const current = get();
// Update state
set((state) => { state.value = 10; });
// Subscribe to changes
const unsubscribe = subscribe((state) => console.log(state));Use get and set when:
- Accessing state in utility functions
- Implementing middleware or side effects
- Working outside React component lifecycle
import { create } from "zustand";
import type { PersistStorage } from "zustand/middleware";
import { persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
export type PersistConfig = {
name: string;
storage?: PersistStorage<unknown>;
partialize?: <T>(state: T) => object;
version?: number;
};
export const StoreBuilder = <T>(
initialState: T,
persistConfig?: PersistConfig,
) => {
type StoreState = T;
// Create store with or without persistence
const useStore = persistConfig
? create<StoreState>()(
persist(
immer((_set) => ({
...initialState,
})),
{
name: persistConfig.name,
storage: persistConfig.storage,
partialize: persistConfig.partialize as (state: StoreState) => object,
version: persistConfig.version,
},
),
)
: create<StoreState>()(
immer((_set) => ({
...initialState,
})),
);
const get = () => useStore.getState();
const set = useStore.setState;
const subscribe = useStore.subscribe;
/**
* Creates a factory function that exposes all store state and methods.
* Facilitates separating actions from the store state, and using the non-reactive get/set isntead of `useStore`, as it needs to be explicity exposed.
* @returns {set} The store state setter
* @returns {useStore} The store state React hook
* @returns {subscribe} The store subscription function
* @returns {Object} The store state and methods
*/
const createFactory = <S>(
args: S,
): (() => T & S & { set: typeof set; subscribe: typeof subscribe }) => {
return () => {
return {
...get(),
set,
subscribe,
...args,
};
};
};
return { get, set, useStore, subscribe, createFactory };
};
Zustand StoreBuilder Pattern Guide
Overview
The StoreBuilder pattern provides a standardized way to create Zustand stores with:
- Type-safe state management
- Optional persistence using Zustand's persist middleware
- Immer middleware for immutable state updates
- Separation of actions from state using the factory pattern
- Exposed non-reactive
get/setfor use outside React components
Core API
const { get, set, useStore, subscribe, createFactory } = StoreBuilder(initialState, persistConfig?)Returns
- `useStore`: React hook for reactive state access in components
- `get`: Non-reactive function to get current state
- `set`: Function to update state (works with immer)
- `subscribe`: Subscribe to state changes
- `createFactory`: Create a factory function that combines state with custom actions
Basic Usage (Without Persistence)
import { StoreBuilder } from './storebuilder';
type CounterState = {
count: number;
increment: () => void;
decrement: () => void;
};
const { get, set, useStore, createFactory } = StoreBuilder<Omit<CounterState, 'increment' | 'decrement'>>({
count: 0,
});
// Create factory with actions separated from state
const useCounterStore = createFactory({
increment: () => set((state) => { state.count += 1; }),
decrement: () => set((state) => { state.count -= 1; }),
});
// Usage in React components
function Counter() {
const { count, increment, decrement } = useCounterStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}With Persistence
import { StoreBuilder } from './storebuilder';
type UserPreferences = {
theme: 'light' | 'dark';
language: string;
setTheme: (theme: 'light' | 'dark') => void;
setLanguage: (language: string) => void;
};
const { get, set, useStore, createFactory } = StoreBuilder<Omit<UserPreferences, 'setTheme' | 'setLanguage'>>(
{
theme: 'light',
language: 'en',
},
{
name: 'user-preferences', // localStorage key
version: 1,
// Optional: only persist specific fields
partialize: (state) => ({ theme: state.theme, language: state.language }),
}
);
const useUserPreferences = createFactory({
setTheme: (theme: 'light' | 'dark') => set((state) => { state.theme = theme; }),
setLanguage: (language: string) => set((state) => { state.language = language; }),
});
// Usage
function Settings() {
const { theme, language, setTheme, setLanguage } = useUserPreferences();
return (
<div>
<select value={theme} onChange={(e) => setTheme(e.target.value as 'light' | 'dark')}>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<input value={language} onChange={(e) => setLanguage(e.target.value)} />
</div>
);
}Complex Example with Async Actions
import { StoreBuilder } from './storebuilder';
type TodoState = {
todos: Array<{ id: string; text: string; completed: boolean }>;
loading: boolean;
error: string | null;
};
type TodoActions = {
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
deleteTodo: (id: string) => void;
fetchTodos: () => Promise<void>;
};
const { get, set, useStore, createFactory } = StoreBuilder<TodoState>({
todos: [],
loading: false,
error: null,
});
const useTodoStore = createFactory<TodoActions>({
addTodo: (text: string) => {
const id = Math.random().toString(36);
set((state) => {
state.todos.push({ id, text, completed: false });
});
},
toggleTodo: (id: string) => {
set((state) => {
const todo = state.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
});
},
deleteTodo: (id: string) => {
set((state) => {
state.todos = state.todos.filter(t => t.id !== id);
});
},
fetchTodos: async () => {
set((state) => { state.loading = true; state.error = null; });
try {
const response = await fetch('/api/todos');
const todos = await response.json();
set((state) => {
state.todos = todos;
state.loading = false;
});
} catch (error) {
set((state) => {
state.error = error instanceof Error ? error.message : 'Unknown error';
state.loading = false;
});
}
},
});
// Usage
function TodoList() {
const { todos, loading, error, addTodo, toggleTodo, deleteTodo } = useTodoStore();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{todos.map(todo => (
<div key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
))}
</div>
);
}Using get and set Outside React Components
// Get current state outside React
const currentCount = get().count;
// Update state outside React
set((state) => { state.count = 10; });
// Subscribe to changes outside React
const unsubscribe = subscribe((state) => {
console.log('State changed:', state);
});
// Later: unsubscribe()Pattern Benefits
1. Type Safety: Full TypeScript support with type inference 2. Immer Integration: Write mutations that are automatically converted to immutable updates 3. Separation of Concerns: State definition separate from actions via createFactory 4. Persistence: Optional localStorage/sessionStorage persistence with fine-grained control 5. Flexibility: Works in React components (via hook) and outside React (via get/set) 6. Non-Reactive Access: Use get() and set() for non-reactive state access when needed
Key Patterns
State vs Actions Separation
Always separate state types from action types using Omit:
type FullState = {
// State
value: number;
// Actions
setValue: (v: number) => void;
};
// Pass only state to StoreBuilder
const { createFactory } = StoreBuilder<Omit<FullState, 'setValue'>>({
value: 0,
});
// Add actions in createFactory
const useMyStore = createFactory({
setValue: (v: number) => set((state) => { state.value = v; }),
});Immer Draft Mutations
With immer middleware, update state by mutating the draft:
// ✅ Correct: Mutate the draft
set((state) => {
state.count += 1;
state.items.push(newItem);
});
// ❌ Incorrect: Don't return new state
set((state) => {
return { ...state, count: state.count + 1 };
});Factory Return Type
The factory combines state, actions, and store methods:
const useMyStore = createFactory({ ...actions });
// Returns: State & Actions & { set, subscribe }
const storeValue = useMyStore();
// Access: storeValue.count, storeValue.increment, storeValue.set, storeValue.subscribe