useReducer Hook

0/3 in this phase0/36 across the roadmap

📖 Concept

useReducer is an alternative to useState that is better suited for complex state logic involving multiple sub-values or when the next state depends on the previous one.

The Reducer Pattern:

  1. State: The current data.
  2. Action: An object describing what happened (e.g., { type: 'INCREMENT' }).
  3. Reducer: A pure function that takes the current state and an action, and returns the new state.
  4. Dispatch: A function used to send actions to the reducer.

When to use useReducer:

  • You have complex state (objects with many fields).
  • One state update depends on another state value.
  • You want to separate state logic from the component's UI code.
  • You want to make testing state transitions easier.

💻 Code Example

codeTap to expand ⛶
1import React, { useReducer } from 'react';
2
3// 1. Initial State
4const initialState = { count: 0, step: 1 };
5
6// 2. Reducer Function (Pure, no side effects!)
7function reducer(state, action) {
8 switch (action.type) {
9 case 'increment':
10 return { ...state, count: state.count + state.step };
11 case 'decrement':
12 return { ...state, count: state.count - state.step };
13 case 'setStep':
14 return { ...state, step: action.payload };
15 case 'reset':
16 return initialState;
17 default:
18 throw new Error('Unknown action type');
19 }
20}
21
22function AdvancedCounter() {
23 // 3. Initialize useReducer
24 const [state, dispatch] = useReducer(reducer, initialState);
25
26 return (
27 <div className="p-8 max-w-sm mx-auto bg-white shadow-xl rounded-2xl">
28 <h1 className="text-2xl font-bold mb-4">Reducer Counter</h1>
29
30 <div className="text-5xl font-mono text-center mb-8">{state.count}</div>
31
32 <div className="flex justify-center space-x-4 mb-6">
33 <button
34 onClick={() => dispatch({ type: 'decrement' })}
35 className="w-12 h-12 rounded-full bg-red-100 text-red-600 text-2xl"
36 >
37 -
38 </button>
39 <button
40 onClick={() => dispatch({ type: 'increment' })}
41 className="w-12 h-12 rounded-full bg-green-100 text-green-600 text-2xl"
42 >
43 +
44 </button>
45 </div>
46
47 <div className="space-y-2">
48 <label className="text-sm text-gray-500 block">Step Size: {state.step}</label>
49 <input
50 type="range" min="1" max="10"
51 value={state.step}
52 onChange={(e) => dispatch({ type: 'setStep', payload: Number(e.target.value) })}
53 className="w-full"
54 />
55 </div>
56
57 <button
58 onClick={() => dispatch({ type: 'reset' })}
59 className="mt-6 w-full py-2 text-gray-400 hover:text-gray-600 text-sm underline"
60 >
61 Reset to Defaults
62 </button>
63 </div>
64 );
65}
66
67export default AdvancedCounter;

🏋️ Practice Exercise

  1. Rewrite a complex useState form (like the one in the previous topic) using useReducer.
  2. Build a 'Shopping Cart' reducer that handles 'ADD_ITEM', 'REMOVE_ITEM', and 'UPDATE_QUANTITY'.
  3. Create a 'Timer' component where useReducer handles 'START', 'STOP', 'TICK', and 'RESET'.
  4. Practice moving your reducer function outside the component file to see how it can be tested independently.

⚠️ Common Mistakes

  • Mutating state inside the reducer (Reducers MUST be pure).

  • Forgetting to return the state in the 'default' case (this can cause the state to become undefined).

  • Using useReducer for very simple state that only needs a single useState.

  • Performing side effects (like API calls) inside a reducer.

💼 Interview Questions

🎤 Mock Interview

Practice a live interview for useReducer Hook