iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…