useReducer is useState's sibling for state that changes through a well-defined set of actions rather than arbitrary direct sets — the same pattern Redux popularized, built into React itself.
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return { count: 0 };
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
{state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>−</button>
</>
);
}
useState | useReducer | |
|---|---|---|
| Best for | Independent, simple values | State with multiple sub-values that change together, or complex transition logic |
| Update via | Direct setX(newValue) calls, scattered wherever needed | Dispatching named actions through one central reducer function |
| Testability | Update logic is inline, harder to isolate | The reducer is a plain function — trivial to unit test in isolation |
A practical signal that it's time to reach for useReducer: several useState calls in one component that always get updated together, or update logic complex enough that it's genuinely hard to follow scattered across several event handlers.