원클릭으로
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 직업 분류 기준
| 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);
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.