Hooks
useReducer for complex state
Reducer patterns, when to prefer it over useState, and testability.
useReducer centralises state transitions in a pure (state, action) => state function, dispatched via actions. Prefer it over useState when the next state depends on the previous, several values change together, or transitions are complex (wizards, editors, undo/redo).
useReducer is a vending machine: press a labelled button (action) and the mechanism (reducer) deterministically produces the next state.
Key concepts
1
Because the reducer is a pure function, it is trivially unit-testable in isolation and keeps components declarative — they dispatch intent, not mechanics.
trivially unit-testable
2
dispatch is referentially stable, so passing it down (or via context) never causes child re-renders — a nice performance property versus passing many setX callbacks.
referentially stabledispatchsetX
3
Pitfall: putting side effects inside the reducer; reducers must stay pure — do effects in useEffect or handlers. Interview angle: "useState vs useReducer?" — reducer for related/complex transitions and testability; state for simple independent values.
Pitfall:Interview angle:useEffect
jsx
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) };
default: return state;
}
}
const [state, dispatch] = useReducer(reducer, { items: [] });
dispatch({ type: 'add', item });