React useReducer Hook: Manage Complex State Like a Pro
Last updated 2 months, 2 weeks ago | 84 views 75 5

Introduction: Why Use useReducer
in React?
Managing state in React often starts with useState
, and for simple counters or toggles, that’s enough. But what if you’re dealing with more complex state logic—like forms, nested objects, or actions with multiple state transitions?
This is where the useReducer
Hook shines. Inspired by Redux-style reducers, useReducer
allows you to manage complex state updates in a predictable and centralized way.
Use it when:
-
Multiple pieces of state are interrelated.
-
Next state depends on previous state.
-
You want clearer state transitions for maintainability.
Understanding useReducer
: Concept and Syntax
Basic Syntax
const [state, dispatch] = useReducer(reducer, initialState);
-
reducer
: A function that handles state transitions. -
initialState
: The starting value of your state. -
dispatch
: A function used to send actions to the reducer.
Reducer Function Pattern
A reducer takes the current state and an action, then returns a new state.
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
Step-by-Step: Using useReducer
in React
1. Define Initial State
const initialState = { count: 0 };
2. Create a Reducer Function
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error('Unknown action type');
}
}
3. Initialize useReducer
const [state, dispatch] = useReducer(reducer, initialState);
4. Dispatch Actions
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
Complete Functional Example: Counter with useReducer
import React, { useReducer } from 'react';
// 1. Initial state
const initialState = { count: 0 };
// 2. Reducer function
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
return state;
}
}
function Counter() {
// 3. Hook setup
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div style={{ textAlign: 'center' }}>
<h2>Count: {state.count}</h2>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
export default Counter;
Comparison Table: useState
vs useReducer
Feature | useState |
useReducer |
---|---|---|
Best for | Simple state | Complex, interrelated state |
State update logic location | Inline in component | Centralized in reducer function |
Suitable for multiple actions | ❌ No | ✅ Yes |
Reducer-like structure | ❌ No | ✅ Yes |
Ease of testing | Medium | High (pure function logic) |
Tips & Common Pitfalls
✅ Best Practices
-
Group related state in one reducer instead of multiple
useState
calls. -
Use action objects to describe state changes clearly.
-
Keep your reducer pure—don’t include side effects like API calls.
⚠️ Common Mistakes
-
Mutating state directly in reducer—always return a new object.
-
Forgetting default case in
switch
—can break your app silently. -
Over-engineering: For simple cases, stick with
useState
.
When Should You Use useReducer
?
Use Case | Recommended Approach |
---|---|
Counter or toggle logic | useState |
Multi-step form or wizard | useReducer |
Complex UI interaction logic | useReducer |
State with many transitions | useReducer |
Global app-wide state | Context + useReducer or Redux |
Conclusion: Mastering useReducer
for Better State Management
The useReducer
Hook is a powerful alternative to useState
when your component's state logic becomes complex or hard to follow. It helps you:
-
Keep state transitions predictable
-
Centralize update logic
-
Improve maintainability as your app scales
Pro Tip: Combine useReducer
with useContext
for lightweight global state management—no Redux required!
✅ Actionable Takeaways
-
Use
useReducer
for structured and testable state logic. -
Always return a new state—never mutate!
-
Simplify your reducer with helper functions if needed.
-
Combine with
useContext
to manage global or shared state effectively.