Files
leo-claude-mktplace/plugins/saas-react-platform/skills/state-patterns.md
lmiranda 2d51df7a42 feat(marketplace): command consolidation + 8 new plugins (v8.1.0 → v9.0.0) [BREAKING]
Phase 1b: Rename all ~94 commands across 12 plugins to /<noun> <action>
sub-command pattern. Git-flow consolidated from 8→5 commands (commit
variants absorbed into --push/--merge/--sync flags). Dispatch files,
name: frontmatter, and cross-reference updates for all plugins.

Phase 2: Design documents for 8 new plugins in docs/designs/.

Phase 3: Scaffold 8 new plugins — saas-api-platform, saas-db-migrate,
saas-react-platform, saas-test-pilot, data-seed, ops-release-manager,
ops-deploy-pipeline, debug-mcp. Each with plugin.json, commands, agents,
skills, README, and claude-md-integration. Marketplace grows from 12→20.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 14:52:11 -05:00

204 lines
5.8 KiB
Markdown

---
name: state-patterns
description: State management patterns — React Context for simple, Zustand for medium, Redux Toolkit for complex
---
# State Management Patterns
## Purpose
Guide state management decisions and provide scaffolding templates for React Context, Zustand, and Redux Toolkit. This skill helps select the right pattern based on complexity and generates consistent store implementations.
---
## Decision Framework
| Criteria | Context | Zustand | Redux Toolkit |
|----------|---------|---------|---------------|
| **Scope** | Single feature, few consumers | Multiple features, medium consumers | App-wide, many consumers |
| **Complexity** | Simple values (theme, locale, auth) | Medium (cart, form wizard, filters) | Complex (normalized entities, async workflows) |
| **Async logic** | Manual with `useEffect` | Built-in with async actions | `createAsyncThunk` with lifecycle |
| **DevTools** | None built-in | Optional middleware | Full Redux DevTools integration |
| **Dependencies** | None (built-in React) | ~2KB, zero config | ~12KB, more boilerplate |
| **Learning curve** | Low | Low | Medium-High |
### Quick Decision
- Need to share a simple value across a few components? **Context**
- Need a store with some async logic and moderate complexity? **Zustand**
- Need normalized state, middleware, complex async flows, or strict patterns? **Redux Toolkit**
## React Context Template
```typescript
// stores/auth-context.tsx
import { createContext, useContext, useReducer, type ReactNode } from 'react';
// State type
interface AuthState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
}
// Action types
type AuthAction =
| { type: 'LOGIN'; payload: User }
| { type: 'LOGOUT' }
| { type: 'SET_LOADING'; payload: boolean };
// Initial state
const initialState: AuthState = {
user: null,
isAuthenticated: false,
isLoading: false,
};
// Reducer
function authReducer(state: AuthState, action: AuthAction): AuthState {
switch (action.type) {
case 'LOGIN':
return { ...state, user: action.payload, isAuthenticated: true, isLoading: false };
case 'LOGOUT':
return { ...state, user: null, isAuthenticated: false };
case 'SET_LOADING':
return { ...state, isLoading: action.payload };
default:
return state;
}
}
// Context
const AuthContext = createContext<{
state: AuthState;
dispatch: React.Dispatch<AuthAction>;
} | null>(null);
// Provider
export function AuthProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(authReducer, initialState);
return (
<AuthContext.Provider value={{ state, dispatch }}>
{children}
</AuthContext.Provider>
);
}
// Hook with validation
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
```
## Zustand Template
```typescript
// stores/cart-store.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartState {
items: CartItem[];
addItem: (item: Omit<CartItem, 'quantity'>) => void;
removeItem: (id: string) => void;
clearCart: () => void;
totalPrice: () => number;
}
export const useCartStore = create<CartState>()(
devtools(
persist(
(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, quantity: i.quantity + 1 } : i
)};
}
return { items: [...state.items, { ...item, quantity: 1 }] };
}),
removeItem: (id) => set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
clearCart: () => set({ items: [] }),
totalPrice: () => get().items.reduce(
(sum, item) => sum + item.price * item.quantity, 0
),
}),
{ name: 'cart-storage' }
)
)
);
```
## Redux Toolkit Template
```typescript
// store/slices/productsSlice.ts
import { createSlice, createAsyncThunk, type PayloadAction } from '@reduxjs/toolkit';
// Async thunk
export const fetchProducts = createAsyncThunk(
'products/fetchAll',
async (_, { rejectWithValue }) => {
try {
const response = await fetch('/api/products');
return await response.json();
} catch (error) {
return rejectWithValue('Failed to fetch products');
}
}
);
// Slice
const productsSlice = createSlice({
name: 'products',
initialState: {
items: [] as Product[],
status: 'idle' as 'idle' | 'loading' | 'succeeded' | 'failed',
error: null as string | null,
},
reducers: {
updateProduct: (state, action: PayloadAction<Product>) => {
const index = state.items.findIndex((p) => p.id === action.payload.id);
if (index !== -1) state.items[index] = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchProducts.pending, (state) => { state.status = 'loading'; })
.addCase(fetchProducts.fulfilled, (state, action) => {
state.status = 'succeeded';
state.items = action.payload;
})
.addCase(fetchProducts.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload as string;
});
},
});
export const { updateProduct } = productsSlice.actions;
export default productsSlice.reducer;
```
## When NOT to Use Global State
- Form input values (use local `useState` or `react-hook-form`)
- UI toggle state (modal open/close) unless shared across routes
- Computed values derivable from existing state (compute inline or `useMemo`)
- Server cache data (use TanStack Query or SWR instead of Redux)