Mastering useReducer
in React: A Step-by-Step Guide for Real-World State Management
In modern React development, managing state predictably is key to building maintainable, scalable applications. While useState
handles simple state needs, useReducer
shines when your component’s state logic becomes more complex or when the next state depends on the previous one.
In this article, we'll break down the useReducer
hook in depth and show how it enables clear, scalable, and testable state transitions. We’ll also build a mathematical counter using reducer patterns, covering advanced JavaScript concepts like the spread operator, custom actions, and strict typing with TypeScript.
What Is useReducer
?
useReducer
is a React hook that provides an alternative to useState
. It’s commonly used when:
- State logic is complex.
- The next state depends on the previous one.
- You need to centralize state transitions.
It works by dispatching actions to a reducer
function, which then returns the new state.
const [state, dispatch] = useReducer(reducerFunction, initialState);
Reducer Function Explained
A reducer is a pure function. Given the current state and an action, it returns a new state. It does not mutate the existing state directly.
type ActionType = {
type: 'increment' | 'decrement' | 'custom';
payload?: number;
};
const reducerCounter = (state: typeof initialState, action: ActionType) => {
switch (action.type) {
case 'increment':
return { ...state, counter: state.counter + 1 };
case 'decrement':
return { ...state, counter: state.counter - 1 };
case 'custom':
return { ...state, counter: state.counter + (action.payload || 0) };
default:
return state;
}
};
What’s the Spread Operator?
The spread operator (...
) allows cloning and extending an object. Here it ensures immutability by copying the old state while updating specific properties.
Real Example: Counter Component Using useReducer
import { useReducer } from 'react';
const initialState = { counter: 10 };
export const CounterRed = () => {
const [state, dispatch] = useReducer(reducerCounter, initialState);
return (
<div className="math-card">
<h3>🧮 Reducer Counter</h3>
<p>Counter: {state.counter}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+1</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
<button onClick={() => dispatch({ type: 'custom', payload: 10 })}>+10</button>
</div>
);
};
Why Prefer useReducer
Over useState
?
Feature | useState |
useReducer |
---|---|---|
Simple state | ✅ Easy | 🚫 Overkill |
Complex state logic | 🚫 Hard to manage | ✅ Centralized |
Depends on previous state | 🔁 Risky | ✅ Predictable |
Testability | 😐 Limited | ✅ Clean and testable |
Bonus Tips for Mastery
- Combine
useReducer
withuseContext
for global state management. - Use
useCallback
to avoid unnecessary renders of dispatch-bound functions. - Structure your actions and types clearly for maintainability.
Final Thoughts
React’s useReducer
provides a powerful toolset for managing state declaratively. It’s especially useful when dealing with branching logic or state updates that are contingent on other values.
By building a simple but extendable mathematical counter, you now have a blueprint to implement this hook in more advanced real-world scenarios—such as forms, API state, and more.
With this knowledge, you’re one step closer to mastering modern React state management with useReducer
.
Tags
react, javascript, hooks, reducer, frontend, typescript, performance
Top comments (0)