一键导入
state-management-patterns
Guide for choosing and implementing state management solutions. Use when deciding how to manage application state.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guide for choosing and implementing state management solutions. Use when deciding how to manage application state.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Checklist for auditing and fixing accessibility issues (WCAG 2.1 AA compliance). Use when reviewing components or pages for accessibility.
Guide for creating RESTful API endpoints with validation and error handling. Use when implementing backend API routes.
Guide for implementing secure authentication with JWT and session management. Use when adding authentication to an application.
Guide for creating responsive layouts with CSS. Use when implementing responsive design or fixing layout issues.
Guide for designing database schemas with Prisma ORM. Use when creating or modifying database models.
Guide for consistent error handling across frontend and backend. Use when implementing error handling logic.
| name | state-management-patterns |
| description | Guide for choosing and implementing state management solutions. Use when deciding how to manage application state. |
Follow this guide to choose the right state management solution:
State Complexity Decision:
┌─ Is it SERVER state (API data)?
│ └─ YES → Use React Query / SWR
│
├─ Is it LOCAL to one component?
│ └─ YES → Use useState / useReducer
│
├─ Is it shared by 2-3 nearby components?
│ └─ YES → Lift state up or use Context
│
├─ Is it shared globally (user, theme, cart)?
│ └─ Small/Medium app → Zustand
│ └─ Large app → Redux Toolkit
│
└─ Is it URL state (filters, pagination)?
└─ YES → Use URL params (useSearchParams)
Use for: API data, caching, synchronization
// hooks/useUsers.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createUser,
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}
// Component
function UserList() {
const { data, isLoading, error } = useUsers();
const createMutation = useCreateUser();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return (
<div>
{data.map(user => <UserCard key={user.id} user={user} />)}
<button onClick={() => createMutation.mutate(newUser)}>
Add User
</button>
</div>
);
}
Benefits:
Use for: Component-specific state
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Use for: Complex state logic with multiple sub-values
type State = {
count: number;
step: number;
};
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'setStep'; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'setStep':
return { ...state, step: action.payload };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
return (
<div>
<p>Count: {state.count}</p>
<input
type="number"
value={state.step}
onChange={e => dispatch({ type: 'setStep', payload: +e.target.value })}
/>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
Use for: Theme, locale, auth - shared by few components
// contexts/ThemeContext.tsx
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// Usage
function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return <button onClick={toggleTheme}>{theme}</button>;
}
⚠️ Context Performance Warning:
Use for: Cart, notifications, user preferences
// stores/cartStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
clearCart: () => void;
total: number;
}
export const useCartStore = create<CartStore>()(
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: [] }),
get total() {
return get().items.reduce((sum, item) => sum + item.price * item.quantity, 0);
},
}),
{
name: 'cart-storage', // localStorage key
}
)
);
// Component
function Cart() {
const { items, total, removeItem } = useCartStore();
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.name} x {item.quantity}
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
<p>Total: ${total}</p>
</div>
);
}
// Only subscribes to total (optimized)
function CartTotal() {
const total = useCartStore(state => state.total);
return <p>Total: ${total}</p>;
}
Benefits:
Use for: Complex apps with many related state slices
// store/slices/authSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
interface AuthState {
user: User | null;
token: string | null;
loading: boolean;
}
export const login = createAsyncThunk(
'auth/login',
async (credentials: { email: string; password: string }) => {
const response = await loginApi(credentials);
return response.data;
}
);
const authSlice = createSlice({
name: 'auth',
initialState: { user: null, token: null, loading: false } as AuthState,
reducers: {
logout: (state) => {
state.user = null;
state.token = null;
},
},
extraReducers: (builder) => {
builder
.addCase(login.pending, (state) => {
state.loading = true;
})
.addCase(login.fulfilled, (state, action) => {
state.user = action.payload.user;
state.token = action.payload.token;
state.loading = false;
})
.addCase(login.rejected, (state) => {
state.loading = false;
});
},
});
export const { logout } = authSlice.actions;
export default authSlice.reducer;
// store/store.ts
import { configureStore } from '@reduxjs/toolkit';
import authReducer from './slices/authSlice';
export const store = configureStore({
reducer: {
auth: authReducer,
},
});
// Component
function LoginButton() {
const dispatch = useAppDispatch();
const { user, loading } = useAppSelector(state => state.auth);
const handleLogin = () => {
dispatch(login({ email, password }));
};
return <button onClick={handleLogin}>Login</button>;
}
Use for: Filters, pagination, search queries
// hooks/useFilters.ts
import { useSearchParams } from 'react-router-dom';
export function useFilters() {
const [searchParams, setSearchParams] = useSearchParams();
const filters = {
search: searchParams.get('search') || '',
category: searchParams.get('category') || 'all',
page: parseInt(searchParams.get('page') || '1'),
};
const setFilters = (newFilters: Partial<typeof filters>) => {
const params = new URLSearchParams(searchParams);
Object.entries(newFilters).forEach(([key, value]) => {
if (value) {
params.set(key, String(value));
} else {
params.delete(key);
}
});
setSearchParams(params);
};
return { filters, setFilters };
}
// Component
function ProductList() {
const { filters, setFilters } = useFilters();
return (
<div>
<input
value={filters.search}
onChange={e => setFilters({ search: e.target.value, page: 1 })}
/>
{/* Results shareable via URL */}
</div>
);
}
Use for: Forms (don't use global state!)
import { useForm } from 'react-hook-form';
function ProfileForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
defaultValues: {
name: user.name,
email: user.email,
},
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
<input {...register('email')} />
</form>
);
}
// Bad: Duplicating server state
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers().then(setUsers);
}, []);
// Good: Use React Query
const { data: users } = useQuery({ queryKey: ['users'], queryFn: fetchUsers });
// Bad: Causes many re-renders
<MousePositionContext.Provider value={{ x, y }}>
// Bad: Redux for a simple toggle
const isOpen = useSelector(state => state.modal.isOpen);
// Good: Local state
const [isOpen, setIsOpen] = useState(false);