Skip to the solution
MediumFrontend6 min read

What is the difference between useState and useReducer?

1.2k views

The problem

Question presented to candidate: "You have a component whose state is getting messy β€” half a dozen useState calls that keep having to change together. When would you reach for useReducer instead, and what does that actually buy you?"

What a strong answer should cover:

  • Both add local state to a function component; they are equivalent in power.
  • useState fits simple, independent values updated directly.
  • useReducer centralises transitions in a pure reducer (state, action) => newState; components dispatch intent rather than computing the next state.
  • The real payoff: the reducer is a pure function testable with no React at all, and branchy transitions live in one place.
  • dispatch has a stable identity across renders, so it can be passed down without breaking React.memo β€” the same is true of the useState setter.
  • Multiple dispatches in one handler are batched into a single re-render.
  • Signal of depth: useState is implemented on the same underlying machinery as useReducer.

Clarifying questions expected:

  • "Do these state fields have to change together, or are they genuinely independent?"
  • "Is this state local, or does it need to be shared β€” in which case is a reducer plus context the right shape?"

Code / implementation expected: Yes β€” a small reducer with two or three action types, and the dispatch call site.

  • hooks
  • state management
  • useState
  • useReducer

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 9 sections

Try it in the playground

Open any example in the playground to run and change it.

ReactA reducer with a throwing default branch, plus batched dispatches
import { useReducer, useState, memo } from "react";

// Pure β€” no React, no side effects. This function alone is unit-testable:
//   expect(reducer({ count: 0 }, { type: "inc" })).toEqual({ count: 1 })
function reducer(state, action) {
  switch (action.type) {
    case "inc":   return { count: state.count + 1 };
    case "dec":   return { count: state.count - 1 };
    case "reset": return { count: 0 };
    // Throwing turns a typo in an action type into an immediate, obvious
    // error instead of a silent no-op.
    default: throw new Error("unknown action: " + action.type);
  }
}

// dispatch is referentially stable, so this memoised child never re-renders
// just because the parent did.
const Controls = memo(function Controls({ dispatch }) {
  console.log("Controls rendered");
  return (
    <p>
      <button onClick={() => dispatch({ type: "dec" })}>-</button>{" "}
      <button onClick={() => dispatch({ type: "inc" })}>+</button>{" "}
      <button onClick={() => dispatch({ type: "reset" })}>reset</button>{" "}
      {/* three dispatches, one re-render */}
      <button onClick={() => { dispatch({ type: "inc" }); dispatch({ type: "inc" }); dispatch({ type: "inc" }); }}>
        +3 (batched)
      </button>
    </p>
  );
});

export default function App() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  const [unrelated, setUnrelated] = useState(0);

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6 }}>
      <h3>Count: {state.count}</h3>
      <Controls dispatch={dispatch} />
      <button onClick={() => setUnrelated((n) => n + 1)}>
        Re-render parent ({unrelated})
      </button>
      <p style={{ color: "#666", fontSize: 13 }}>
        Watch the console: clicking the parent button re-renders App, but
        Controls does not re-render β€” dispatch keeps the same identity.
      </p>
    </div>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 28 of 119 in React.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All React.js questions
Up nextMedium

What is the purpose of the `deps` array in `useEffect` and `useCallback`?

Continue