
React useReducer Hook Explained with Real Examples and Best Practices (2025 Guide)
- bilalshafqat42
- June 30, 2025
- Blog, React Hooks, React Js
- 0 Comments
Confused about when to use useReducer
in React? Learn how this powerful hook helps manage complex state logic, with real examples and best practices for 2025.
Introduction
Managing state in React is easy — until it’s not. When your component’s state logic becomes complex, using multiple useState
calls can quickly get messy and hard to maintain.
This is where useReducer
comes in. It gives you a structured way to manage state transitions, especially when those transitions depend on the previous state.
In this guide, you’ll learn:
- What
useReducer
is - When to use it vs
useState
- How it compares to Redux
- How to structure reducers
- Real-world examples and best practices
What is useReducer?
useReducer
is a React hook that’s an alternative to useState
for managing more complex state.
It works similarly to Redux by using a reducer function and a dispatch method:
const [state, dispatch] = useReducer(reducer, initialState);
This hook returns the current state and a dispatch function you can use to trigger state updates based on actions.
Why Use useReducer Instead of useState?
useReducer
shines in components where:
- There are multiple pieces of state
- State updates are conditional or depend on previous state
- You want to centralize state logic in one function
useState
is great for simple values like booleans, strings, and numbers — but when your state logic grows in complexity, useReducer
helps keep it maintainable.
Basic Syntax of useReducer
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
Usage:
<button onClick={() => dispatch({ type: "increment" })}>
{state.count}
</button>
useReducer vs useState
Feature | useState | useReducer |
---|---|---|
Ease of use | ✅ Simple | ⚠️ Slightly verbose |
Best for | Simple state | Complex or conditional logic |
State separation | Multiple variables | Single object |
Predictability | Less structured | Centralized logic |
Example: Toggle & Counter Together
const initialState = { count: 0, show: true };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, count: state.count + 1 };
case "toggle":
return { ...state, show: !state.show };
default:
return state;
}
}
const [state, dispatch] = useReducer(reducer, initialState);
Now both count
and show
are controlled in one predictable place.
useReducer + useContext for Global State
Want to manage shared state across multiple components without Redux?
Use useReducer
inside a context provider:
const AuthContext = createContext();
function AuthProvider({ children }) {
const [state, dispatch] = useReducer(authReducer, { isAuthenticated: false });
return (
<AuthContext.Provider value={{ state, dispatch }}>
{children}
</AuthContext.Provider>
);
}
Common Mistakes to Avoid
1. Returning the old state instead of a new copy
// ❌ Mutating state
state.count++;
// ✅ Correct
return { ...state, count: state.count + 1 };
2. Handling too many unrelated things in one reducer
Split reducers or use multiple reducers with combineReducers
(in Redux) or custom logic in React.
3. Using it when useState would be simpler
If you just need to toggle a boolean or store a single input, stick with useState()
.
When to Use useReducer (Best Scenarios)
- Complex forms with validation logic
- Multi-step workflows (wizards, tabs)
- State objects with multiple sub-properties
- Redux-like global stores without external libraries
- Reusable UI components with configurable state behavior
Best Practices for useReducer (2025)
- Always return new state objects (avoid mutation)
- Use constants for action types to reduce typos
- Group related state into one reducer
- Combine with
useContext
for lightweight global state - Use action creators for better readability
Final Thoughts
useReducer()
gives you better structure and flexibility than useState()
when state logic becomes complex or when you need shared logic across multiple components.
Use it when:
- You have multiple values that change together
- Your state transitions are based on conditions
- You want to extract state logic for readability
Don’t replace useState
in every case — but when you need it, useReducer
makes your code scalable and maintainable.