State Management
React state management: useState, useReducer, Context, Zustand, Redux, Jotai. Choose by scope + complexity.
React — state management
EXAMPLE
// ===== The mental model =====
// Local state: one component, useState
// Lifted state: shared between siblings, useState in parent
// Cross-tree: useContext, Zustand, Redux, Jotai
// Server state: TanStack Query, SWR (separate concern from client state)
// ===== useState (local) =====
const [count, setCount] = useState(0);
setCount(c => c + 1); // functional update for stale-state safety
// ===== useReducer (complex local) =====
function reducer(state, action) {
switch (action.type) {
case 'add': return { ...state, items: [...state.items, action.item] };
case 'remove': return { ...state, items: state.items.filter(i => i.id !== action.id) };
case 'reset': return { items: [] };
default: throw new Error('unknown action');
}
}
const [state, dispatch] = useReducer(reducer, { items: [] });
dispatch({ type: 'add', item: { id: 1 } });
// ===== Context (cross-tree, infrequent updates) =====
const ThemeContext = createContext('light');
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Page />
</ThemeContext.Provider>
);
}
function Page() {
const { theme } = useContext(ThemeContext);
return <div className={theme}>...</div>;
}
// Note: changing context value re-renders ALL consumers; not great for hot state.
// ===== Zustand (lightweight, recommended for new apps) =====
// npm install zustand
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
inc: () => set((s) => ({ count: s.count + 1 })),
reset: () => set({ count: 0 }),
}));
function Counter() {
const count = useStore((s) => s.count);
const inc = useStore((s) => s.inc);
return <button onClick={inc}>{count}</button>;
}
// Selectors prevent re-renders when other slices change.
// ===== Redux Toolkit (when ecosystem matters) =====
// npm install @reduxjs/toolkit react-redux
import { createSlice, configureStore } from '@reduxjs/toolkit';
import { Provider, useSelector, useDispatch } from 'react-redux';
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [] },
reducers: {
add: (state, action) => { state.items.push(action.payload); }, // Immer makes this OK
remove: (state, action) => { state.items = state.items.filter(i => i.id !== action.payload); },
},
});
const store = configureStore({ reducer: { cart: cartSlice.reducer } });
function Cart() {
const items = useSelector((s) => s.cart.items);
const dispatch = useDispatch();
return <button onClick={() => dispatch(cartSlice.actions.add({ id: 1 }))}>Add</button>;
}
// ===== Jotai (atomic) =====
// npm install jotai
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
const doubleAtom = atom((get) => get(countAtom) * 2);
function App() {
const [count, setCount] = useAtom(countAtom);
const [double] = useAtom(doubleAtom);
return <button onClick={() => setCount(c => c + 1)}>{count} / {double}</button>;
}
// ===== Decision tree =====
// One component -> useState
// Complex state machine -> useReducer
// Theme / auth / locale -> Context
// Cross-tree feature state -> Zustand
// Big app + middleware needed -> Redux Toolkit
// Atomic + derived state -> Jotai
// Server state -> TanStack Query
// ===== Pitfalls =====
// - Context for hot state -> all consumers re-render
// - Redux for tiny apps -> overkill
// - Multiple state libraries in one app
// - Storing server data in client state instead of TanStack Query
Why it matters
Pick by scope: useState/useReducer local, Context for cross-tree infrequent, Zustand for cross-tree hot, Redux Toolkit when ecosystem + middleware matter, Jotai for atomic, TanStack Query for server. Default to Zustand + TanStack Query in 2026; reach for Redux when you have a clear reason.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Per scope: useState (component), useReducer + Context (app slice), // Zustand / Redux / Jotai (global), TanStack Query (server state).Try it Yourself »
Discussion
Loading…