React State Management
Comprehensive guide to modern React state management patterns, from local component state to global stores and server state synchronization.
When to Use This Skill
- Setting up global state management in a React app
- Choosing between Redux Toolkit, Zustand, or Jotai
- Managing server state with React Query or SWR
- Implementing optimistic updates
- Debugging state-related issues
- Migrating from legacy Redux to modern patterns
Core Concepts
1. State Categories
| Type |
Description |
Solutions |
| Local State |
Component-specific, UI state |
useState, useReducer |
| Global State |
Shared across components |
Redux Toolkit, Zustand, Jotai |
| Server State |
Remote data, caching |
React Query, SWR, RTK Query |
| URL State |
Route parameters, search |
React Router, nuqs |
| Form State |
Input values, validation |
React Hook Form, Formik |
2. Selection Criteria
Small app, simple state → Zustand or Jotai
Large app, complex state → Redux Toolkit
Heavy server interaction → React Query + light client state
Atomic/granular updates → Jotai
Quick Start
Zustand (Simplest)
// store/useStore.ts
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface AppState {
user: User | null
theme: 'light' | 'dark'
setUser: (user: User | null) => void
toggleTheme: () => void
}
export const useStore = create<AppState>()(
devtools(
persist(
(set) => ({
user: null,
theme: 'light',
setUser: (user) => set({ user }),
toggleTheme: () => set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light'
})),
}),
{ name: 'app-storage' }
)
)
)
// Usage in component
function Header() {
const { user, theme, toggleTheme } = useStore()
return (
<header className={theme}>
{user?.name}
<button Theme</button>
</header>
)
}
Detailed patterns and worked examples
Detailed pattern documentation lives in references/details.md. Read that file when the navigation tier above is insufficient.
Best Practices
Do's
- Colocate state - Keep state as close to where it's used as possible
- Use selectors - Prevent unnecessary re-renders with selective subscriptions
- Normalize data - Flatten nested structures for easier updates
- Type everything - Full TypeScript coverage prevents runtime errors
- Separate concerns - Server state (React Query) vs client state (Zustand)
Don'ts
- Don't over-globalize - Not everything needs to be in global state
- Don't duplicate server state - Let React Query manage it
- Don't mutate directly - Always use immutable updates
- Don't store derived data - Compute it instead
- Don't mix paradigms - Pick one primary solution per category
Migration Guides
From Legacy Redux to RTK
// Before (legacy Redux)
const ADD_TODO = "ADD_TODO";
const addTodo = (text) => ({ type: ADD_TODO, payload: text });
function todosReducer(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [...state, { text: action.payload, completed: false }];
default:
return state;
}
}
// After (Redux Toolkit)
const todosSlice = createSlice({
name: "todos",
initialState: [],
reducers: {
addTodo: (state, action: PayloadAction<string>) => {
// Immer allows "mutations"
state.push({ text: action.payload, completed: false });
},
},
});
1---2name: react-state-management3description: Master modern React state management with Redux Toolkit, Zustand, Jotai, and React Query. Use when setting up global state, managing server state, or choosing between state management solutions.4---5
6# React State Management
7
8Comprehensive guide to modern React state management patterns, from local component state to global stores and server state synchronization.
9
10## When to Use This Skill
11
12- Setting up global state management in a React app
13- Choosing between Redux Toolkit, Zustand, or Jotai
14- Managing server state with React Query or SWR
15- Implementing optimistic updates
16- Debugging state-related issues
17- Migrating from legacy Redux to modern patterns
18
19## Core Concepts
20
21### 1. State Categories
22
23| Type | Description | Solutions |
24| ---------------- | ---------------------------- | ----------------------------- |
25| **Local State** | Component-specific, UI state | useState, useReducer |
26| **Global State** | Shared across components | Redux Toolkit, Zustand, Jotai |
27| **Server State** | Remote data, caching | React Query, SWR, RTK Query |
28| **URL State** | Route parameters, search | React Router, nuqs |
29| **Form State** | Input values, validation | React Hook Form, Formik |
30
31### 2. Selection Criteria
32
33```
34Small app, simple state → Zustand or Jotai
35Large app, complex state → Redux Toolkit
36Heavy server interaction → React Query + light client state
37Atomic/granular updates → Jotai
38```
39
40## Quick Start
41
42### Zustand (Simplest)
43
44```typescript
45// store/useStore.ts
46import { create } from 'zustand'
47import { devtools, persist } from 'zustand/middleware'
48
49interface AppState {
50 user: User | null
51 theme: 'light' | 'dark'
52 setUser: (user: User | null) => void
53 toggleTheme: () => void
54}
55
56export const useStore = create<AppState>()(
57 devtools(
58 persist(
59 (set) => ({
60 user: null,
61 theme: 'light',
62 setUser: (user) => set({ user }),
63 toggleTheme: () => set((state) => ({
64 theme: state.theme === 'light' ? 'dark' : 'light'
65 })),
66 }),
67 { name: 'app-storage' }
68 )
69 )
70)
71
72// Usage in component
73function Header() {
74 const { user, theme, toggleTheme } = useStore()
75 return (
76 <header className={theme}>
77 {user?.name}
78 <button onClick={toggleTheme}>Toggle Theme</button>
79 </header>
80 )
81}
82```
83
84## Detailed patterns and worked examples
85
86Detailed pattern documentation lives in `references/details.md`. Read that file when the navigation tier above is insufficient.
87
88## Best Practices
89
90### Do's
91
92- **Colocate state** - Keep state as close to where it's used as possible
93- **Use selectors** - Prevent unnecessary re-renders with selective subscriptions
94- **Normalize data** - Flatten nested structures for easier updates
95- **Type everything** - Full TypeScript coverage prevents runtime errors
96- **Separate concerns** - Server state (React Query) vs client state (Zustand)
97
98### Don'ts
99
100- **Don't over-globalize** - Not everything needs to be in global state
101- **Don't duplicate server state** - Let React Query manage it
102- **Don't mutate directly** - Always use immutable updates
103- **Don't store derived data** - Compute it instead
104- **Don't mix paradigms** - Pick one primary solution per category
105
106## Migration Guides
107
108### From Legacy Redux to RTK
109
110```typescript
111// Before (legacy Redux)
112const ADD_TODO = "ADD_TODO";
113const addTodo = (text) => ({ type: ADD_TODO, payload: text });
114function todosReducer(state = [], action) {
115 switch (action.type) {
116 case ADD_TODO:
117 return [...state, { text: action.payload, completed: false }];
118 default:
119 return state;
120 }
121}
122
123// After (Redux Toolkit)
124const todosSlice = createSlice({
125 name: "todos",
126 initialState: [],
127 reducers: {
128 addTodo: (state, action: PayloadAction<string>) => {
129 // Immer allows "mutations"
130 state.push({ text: action.payload, completed: false });
131 },
132 },
133});
134```