codejump

AcademyReact

Choosing Between useReducer and useState

useReducer is useState generalized: a pure reducer plus dispatch instead of a raw setter, and when that trade is worth making.

3 min readUpdated

3 exercises

Waiting at the end of this page.

useReducer is useState, generalized

useState gives you a value and a setter. useReducer gives you a value and a dispatch function, plus a reducer you write yourself that decides, for every possible action, what the next state is:

JSX
function counterReducer(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:           return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });
  return (
    <button onClick={() => dispatch({ type: 'increment' })}>
      {state.count}
    </button>
  );
}

Nothing here is unreachable with useState — this is the same counter you could write with a setter. The difference is where the logic lives. With useState, "what happens on increment" is written inline, in the click handler, at the call site. With useReducer, it is written once, in counterReducer, as a plain function that takes a state and an action and returns a state — no JSX, no hooks, nothing React-specific about it at all.

When useState is enough

Reach for useState when each piece of state changes independently and the update is a direct computation — setOpen(!open), setValue(e.target.value), setCount(c => c + 1). There is no branching logic to centralize; writing a reducer around a single boolean toggle just moves one line into a switch statement.

When useReducer earns its place

Two signals, either one enough on its own:

  • The next state depends on more than the action itself — several related values need to change together in one atomic step (a form's values, errors and touched all shifting from a single "field changed" event), or the same event needs different outcomes depending on the current state (a toggle action that behaves differently while status is 'loading').
  • The set of things that can happen is worth naming — a reducer's switch is a complete, readable list of every transition the state can go through: 'fetchStart', 'fetchSuccess', 'fetchError', 'reset'. Scattered setState calls across several handlers give you no single place that answers "what can happen to this state?"
JSX
function formReducer(state, action) {
  switch (action.type) {
    case 'change':
      return { ...state, values: { ...state.values, [action.field]: action.value } };
    case 'blur':
      return { ...state, touched: { ...state.touched, [action.field]: true } };
    case 'reset':
      return initialFormState;
    default:
      return state;
  }
}

Three fields' worth of useState calls, each needing its own change handler, collapse into one dispatch({ type: 'change', field, value }) call site reused for every input.

The part that makes useReducer worth learning: testability

A reducer is a pure function — same state and action in, same new state out, no rendering, no hooks, no component involved:

JavaScript
expect(counterReducer({ count: 5 }, { type: 'reset' })).toEqual({ count: 0 });

You can write that test without React, without a DOM, without rendering anything. State logic exercised through useState setters can only be tested by rendering the component and simulating interaction — a reducer's logic can be tested in isolation, which is the single strongest practical argument for reaching for it once state transitions get complicated enough to be worth testing on their own.

What interviewers are checking

Not whether you can write a reducer — whether you can articulate the trade. useState is simpler and should stay the default; useReducer is the right upgrade exactly when the transitions themselves, not just the values, are the part worth naming and testing.

Now practice it

2 challenges · 1 quiz

Reading this page is the cheap half. These are the exercises that make you use it.