When to Choose useReducer Over useState0%

When to Choose useReducer Over useState

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

When to Choose useReducer Over useState

1. The Limitations of useState in Complex Scenarios

For simple, isolated pieces of state (like toggling a boolean, incrementing a counter, or holding text in an input), useState is clean and sufficient.

However, as applications grow, you frequently encounter state logic where:

  • Multiple pieces of state always change together in response to a single event.
  • The next state depends on complex calculations involving multiple existing state properties.
  • State transitions follow a strict business workflow or finite state machine (e.g. idle -> fetching -> success or error).
  • Business rules and state mutations become scattered across multiple event handler functions throughout your component.

In these scenarios, managing state through disjointed useState setters causes synchronization bugs, race conditions, and messy code.

The solution is the useReducer hook.

Output
useState:
Direct, imperative state mutations scattered across component handlers:
handleClick: setUser(u), setStatus('ok'), setError(null), setCart([])
 
useReducer:
Declarative actions dispatched to a centralized, pure reducer function:
handleClick: dispatch({ type: 'LOGIN_SUCCESS', payload: u })

2. What is useReducer?

useReducer is an alternative to useState modeled after Redux and functional programming state machines.

Instead of calling setter functions directly, components dispatch actions (plain JavaScript objects describing what happened). A centralized, pure function called the reducer receives the current state and the action, and computes the next state:

$$\text{NextState} = \text{reducer}(\text{CurrentState}, \text{Action})$$

3. Comparing useState vs useReducer in Code

With useState (Scattered, Fragile):

JSX
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
 
const handleFetch = async () => {
// 3 separate state setters! Easy to forget one or get out of sync
setLoading(true);
setError(null);
try {
const res = await fetch('/api/courses');
const json = await res.json();
setData(json);
setLoading(false);
} catch (err) {
setError(err);
setLoading(false);
}
};
}

With useReducer (Centralized, Predictable):

JSX
import React, { useReducer } from 'react';
 
// Pure reducer function defined OUTSIDE component
function fetchReducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true, error: null };
case 'FETCH_SUCCESS':
return { ...state, loading: false, data: action.payload };
case 'FETCH_ERROR':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
}
 
export default function DataFetcher() {
const [state, dispatch] = useReducer(fetchReducer, {
data: null,
loading: false,
error: null
});
 
const handleFetch = async () => {
dispatch({ type: 'FETCH_START' });
try {
const res = await fetch('/api/courses');
const json = await res.json();
dispatch({ type: 'FETCH_SUCCESS', payload: json });
} catch (err) {
dispatch({ type: 'FETCH_ERROR', payload: err.message });
}
};
 
return (
<div>
{state.loading && <p>Loading...</p>}
{state.error && <p className="text-red">Error: {state.error}</p>}
<button onClick={handleFetch}>Fetch Catalog</button>
</div>
);
}

4. Decision Matrix: useState vs useReducer

CriterionuseStateuseReducer
Data StructurePrimitives, independent valuesComplex objects, nested arrays
Transition ComplexitySimple (direct assignment)Complex business logic, state machines
State DependenciesNext state is independentNext state depends on multiple fields
TestabilityHarder to test isolated logicTrivial: Reducer is a pure JS function!
Code OrganizationSetters scattered in handlersLogic centralized in one place

Interactive Quiz

Practice Quiz

Question 1
What is the primary role of the useReducer hook in React?
Question 2
What are the two arguments passed to a standard reducer function?
Question 3
Why is a reducer function defined OUTSIDE the component body?
Question 4
When should you prefer useReducer over useState?
Question 5
What does the useReducer hook return when invoked?
Next Lesson

Reducer Functions, Actions, and Dispatch

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext