skip to content

useReducer

You will learn the reducer hook's three-argument signature, why dispatch keeps a stable identity across renders, and how pure reducers make multi-field transitions testable. Interviewers ask when you would reach for useReducer over useState to hear you talk about coupled state and transition logic, not just "it's like Redux".

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In React, what arguments does useReducer take and what does it return, and what happens when you call the dispatch function it gives you?

level: juniorimportance: must knowfreq 68%

answer

  1. three in, two out
  2. a function and a way to signal it
  3. the returned pair, not an object
  4. dispatch queues, it does not compute
  5. init(initialArg) when the third argument exists

basics

~20 s

useReducer takes a reducer function, an initial value, and an optional init function. It returns the current state and a dispatch function; calling dispatch with an action schedules an update whose next state React computes by running the reducer.

solid answer

~40 s

`useReducer(reducer, initialArg, init)` returns a two-element array, `[state, dispatch]`, which you normally destructure. The `reducer` is a function `(state, action) => nextState`: React calls it with the current state and whatever value you dispatched, and uses the return value as the new state. `initialArg` is the initial state unless you pass a third argument `init`, in which case the initial state is `init(initialArg)`. Calling `dispatch(action)` does not run the reducer inline and does not return the next state — it queues the action; React processes the queue and re-renders the component with the new state. Inside the current render the `state` variable still holds the value that render was created with. The action can be any value, but the near-universal convention is an object with a `type` field.

go deeper

for a junior

Be able to write the destructuring line from memory and say what each of the three arguments is for. State plainly that the reducer returns the next state and that dispatch only signals an intent.

for a middle

Explain that dispatch queues the action and that the reducer runs during the render React schedules, so the state variable in the current closure never changes. Mention that multiple dispatches in one handler collapse into a single render.

for a senior

Show that you treat the reducer as an independently testable pure function and that you know the action shape is convention, not API. Be ready to say how you would type the action union and what you do with an unknown action type in production.

for a principal

Frame the choice as where transition logic should live: at every call site or in one named function. Be ready to argue when standardising on a reducer shape across a codebase pays for its extra ceremony and when it is cargo cult.

## The signature `useReducer` is React's second built-in state hook. Its full form is: ```js const [state, dispatch] = useReducer(reducer, initialArg, init); ``` There are three arguments and exactly two return values. - **`reducer`** — a function you write with the shape `(state, action) => nextState`. React owns when it is called; you only describe the transition. - **`initialArg`** — the value used to establish state on the first render. - **`init`** (optional) — if you pass it, the initial state becomes `init(initialArg)` rather than `initialArg` itself. React calls it only when the component first mounts. The return value is always an array of length two, so it is destructured positionally. The names `state` and `dispatch` are convention, not API: `const [cart, dispatchCart] = useReducer(cartReducer, emptyCart)` is equally valid. ## What the reducer is A reducer is a plain function that lives outside React. It takes the state React currently holds and the action you dispatched, and returns the state React should hold next. It must be **pure**: same inputs, same output, no fetching, no timers, no mutation of the state it was handed, no writing to anything outside itself. Because it is just a function, you can call it directly in a unit test with a made-up state and a made-up action and assert on the result — no component, no renderer. ```js function counterReducer(state, action) { switch (action.type) { case 'increment': return { ...state, count: state.count + 1 }; case 'reset': return { ...state, count: 0 }; default: throw new Error('Unknown action: ' + action.type); } } ``` Note the spread: the reducer returns a **new** object rather than editing the one it received. ## What the action is React places no constraint on the action — it is simply the argument you pass to `dispatch`, forwarded verbatim as the reducer's second parameter. It can be a string, a number, or an object. In practice teams use an object with a `type` string plus any payload fields, because a `switch` on `type` reads well and TypeScript can turn it into a discriminated union. That is a convention of the ecosystem, not something React enforces. ## What dispatch actually does Calling `dispatch(action)` **queues** the action and schedules a re-render. It returns `undefined`. It does not call your reducer on the spot, and it does not hand you the next state. During the next render React runs the reducer over the queued actions, starting from the current state, and the resulting value is what the `useReducer` call returns for that render. A direct consequence trips up newcomers: ```js function handleClick() { dispatch({ type: 'increment' }); console.log(state.count); // still the old value } ``` The `state` variable belongs to the render that created this handler; a dispatch cannot reach back and change it. If you need the value after the transition, compute it with the reducer yourself or read it in the next render. Because React queues actions, dispatching several times in one handler is fine — each action is applied to the result of the previous one, and you get one re-render, not three. ## The lazy `init` argument When initial state is expensive to build, or when you want the same builder available for a reset, pass `init`: ```js const [state, dispatch] = useReducer(reducer, userId, createInitialState); ``` Here React computes `createInitialState(userId)` on mount and uses that as the first state. ## The other return value's identity React guarantees that `dispatch` keeps the same identity for the life of the component. You can pass it down, put it in context, or leave it out of a dependency array without churn — unlike the reducer itself, if you define it inside the component body. ## Where it sits next to useState `useState` and `useReducer` are both state; the difference is where the transition logic lives. With `useState` the caller computes the next value at the call site. With `useReducer` the caller names an intent and one function, written once, decides what the next state is. That is why the reducer version scales better when a single user action has to move several fields at once.

  • Does dispatch return the next state, and can you read the updated state on the line after dispatching?
    No on both counts. `dispatch` returns `undefined` and only queues the action; React runs the reducer during the next render. The `state` variable in your handler belongs to the render that created that handler, so it still holds the old value. If you need the next value immediately, call the reducer yourself, or move the work into the reducer.
  • Does the action have to be an object with a type field?
    No — React forwards whatever you pass to `dispatch` as the reducer's second argument, so a string or number works. The `{ type, ...payload }` object is a convention because it switches cleanly and models well as a TypeScript discriminated union. Nothing in React inspects `type`.
  • What happens if you dispatch three times inside one event handler?
    All three actions are queued and applied in order — each one runs against the result of the previous — and React re-renders once with the final state. Dispatching in a loop is therefore safe; you do not pay a render per action.

saying these in an interview costs you the question

  • Says dispatch returns the new state
  • Reads state right after dispatch and expects the new value
  • Thinks React requires actions to be objects with a type
  • Describes the reducer as running immediately when you dispatch
  • Claims useReducer returns an object with state and dispatch keys

context

open as a page

A React component's reducer runs `state.items.push(item); return state;` and the list on screen never updates. Why does nothing re-render, and what rule is the reducer breaking?

level: middleimportance: must knowfreq 55%

basics

~20 s

The reducer mutated the existing state and returned the same object, so React's identity comparison sees no change and bails out of re-rendering. A reducer must be pure: never mutate the state it receives, and return a new object when anything changed.

open as a page

A React component has grown to eight useState calls whose values always move together, and each handler now sets three or four of them in sequence. Would you convert it to useReducer, and what do you actually gain?

level: seniorimportance: must knowfreq 62%

basics

~20 s

Yes — that is the canonical signal. Coupled fields updated together belong in one reducer, which moves the transition logic out of the handlers into one pure function, makes impossible combinations unrepresentable, and gives you something you can unit-test without rendering.

open as a page

React's useReducer returns a dispatch function. Is that function's identity stable across re-renders, and what does the guarantee let you do?

level: middleimportance: should knowfreq 50%

basics

~20 s

Yes. React guarantees the dispatch function returned by useReducer keeps the same identity for the component's whole lifetime, so it can be omitted from dependency arrays, passed to memoized children, and put in context without causing churn.

open as a page

What does the third argument in React's useReducer(reducer, initialArg, init) do, and when is passing it worth it?

level: middleimportance: should knowfreq 38%

basics

~20 s

The third argument is a lazy initializer: React sets the initial state to init(initialArg) instead of initialArg, calling it only when the component mounts. It avoids rebuilding expensive initial state on every render and gives you one named function that also serves a reset.

open as a page