From harness-claude
Splits large Zustand stores into composable slice functions using StateCreator for modular, maintainable state management. Use when stores exceed 100 lines, have 5+ concerns, or need cross-feature composition.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Split large Zustand stores into composable slice functions for modular, maintainable state management
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.
Creates and manages Zustand stores for React state management, covering creation, selectors, actions, updates, and performance best practices like shallow equality.
Creates Zustand stores with TypeScript types, subscribeWithSelector middleware, separated state/actions, individual selectors, and non-React subscriptions. For React state management.
Share bugs, ideas, or general feedback.
Split large Zustand stores into composable slice functions for modular, maintainable state management
set, get, and returns a partial store interface.StateCreator with the full store type as the first generic and the slice type as the second.create call by spreading them.get() — the store is unified at runtime.// stores/slices/auth-slice.ts
import { StateCreator } from 'zustand';
import { AppStore } from '../app-store';
export interface AuthSlice {
user: { id: string; name: string } | null;
isAuthenticated: boolean;
login: (user: { id: string; name: string }) => void;
logout: () => void;
}
export const createAuthSlice: StateCreator<AppStore, [], [], AuthSlice> = (set) => ({
user: null,
isAuthenticated: false,
login: (user) => set({ user, isAuthenticated: true }),
logout: () => set({ user: null, isAuthenticated: false }),
});
// stores/slices/cart-slice.ts
import { StateCreator } from 'zustand';
import { AppStore } from '../app-store';
export interface CartSlice {
items: Array<{ id: string; name: string; qty: number }>;
addItem: (item: { id: string; name: string }) => void;
clearCart: () => void;
}
export const createCartSlice: StateCreator<AppStore, [], [], CartSlice> = (set, get) => ({
items: [],
addItem: (item) =>
set((state) => {
const existing = state.items.find((i) => i.id === item.id);
if (existing) {
return { items: state.items.map((i) => (i.id === item.id ? { ...i, qty: i.qty + 1 } : i)) };
}
return { items: [...state.items, { ...item, qty: 1 }] };
}),
clearCart: () => {
// Cross-slice access — read auth state from cart slice
const user = get().user;
if (!user) return;
set({ items: [] });
},
});
// stores/app-store.ts
import { create } from 'zustand';
import { AuthSlice, createAuthSlice } from './slices/auth-slice';
import { CartSlice, createCartSlice } from './slices/cart-slice';
export type AppStore = AuthSlice & CartSlice;
export const useAppStore = create<AppStore>()((...a) => ({
...createAuthSlice(...a),
...createCartSlice(...a),
}));
StateCreator generic parameters: StateCreator<FullStore, Middlewares, SetMiddlewares, SliceType>. The first generic is the full combined store type so that get() returns the complete interface. The last generic is this slice's contribution.
With middleware: When using middleware (persist, immer, devtools), the middleware array generics must be threaded through:
export const createAuthSlice: StateCreator<
AppStore,
[['zustand/immer', never], ['zustand/devtools', never]],
[],
AuthSlice
> = (set) => ({
/* ... */
});
Cross-slice communication: Since all slices share one store, any slice can read any other slice's state via get(). This is simpler than Redux's cross-slice communication but requires discipline — avoid circular dependencies between slices.
Testing slices independently: Create a test store with only the slice under test plus mock values for other slices:
const useTestStore = create<AppStore>()((...a) => ({
...createAuthSlice(...a),
// Mock cart slice
items: [],
addItem: vi.fn(),
clearCart: vi.fn(),
}));
When to split vs keep together: Split when a slice can be described in one sentence ("manages cart items") and has no business logic coupling with other slices. Keep together when state and actions are tightly interleaved.
https://zustand.docs.pmnd.rs/guides/slices-pattern