React
medium
mid
useState vs useReducer — which one, and when?
useState is direct value-replacement, ideal for independent primitives or small objects. useReducer centralizes complex transitions in a pure function, ideal when next-state depends on the action *and* current state in non-trivial ways.
5 min read·~10 min to think through
Both hooks store state inside the same React fiber slot. The difference is the API shape and the discipline it encourages:
useState—[value, setValue]. Cheap, direct, and the right default. Use for booleans, strings, numbers, or small objects whose updates don't depend on each other.useReducer—[state, dispatch]. Forces you to express transitions as(state, action) => state. The pure-function shape makes complex multi-field updates testable and debuggable.
Reach for useReducer when:
- Several pieces of state always update together (e.g.
{ status, data, error }). - Next state depends on current state and a non-trivial action (wizard steps, undo/redo).
- You want to pass a stable
dispatchreference deep into a tree without prop-drillingsetXcallbacks (dispatchis referentially stable).
Both can be lazily initialized — useState(() => expensive()) and useReducer(reducer, initialArg, init).
A pragmatic test: if you find yourself writing setX(prev => ({ ...prev, ... })) and reaching across multiple fields, you've outgrown useState and reducer will read better.
Code
Follow-up questions
- •How do you lazily initialize state with useReducer?
- •Why is dispatch referentially stable across renders?
- •When would you graduate from useReducer to a state library (Zustand, Redux)?
Common mistakes
- •Mutating state inside the reducer — must return a new object.
- •Putting derived values in state instead of computing them in render.
- •Using useReducer for a single boolean — overkill.
Performance considerations
- •Both bail out of re-render when the new value is `===` to the old.
- •Dispatch is stable, so passing it through context doesn't bust child memoization.
Edge cases
- •An action object created inline inside render is fine — it's the dispatched value, not a dep.
- •useReducer with a non-pure reducer breaks Strict Mode double-invoke detection.
Real-world examples
- •Form-state managers (react-hook-form internals, formik) use reducer-like patterns under the hood for predictable transitions.
Senior engineer discussion
Senior signal: discuss when reducer + context becomes a state-management framework, the cost of context for high-churn state, and when to switch to an external store with selector subscriptions.
Related questions
React
Medium
hot
7 min