[React.js] - Part 13. React Hooks - useReducer Hook

Ace Lennox
The useReducer
Hook in React is a powerful alternative to useState
, particularly when managing complex state logic. This guide explores the useReducer
Hook with clear and simple examples to help you understand its syntax and use cases.
What is useReducer
?
The useReducer
Hook provides a way to handle state with custom logic. It is particularly useful when:
Your state management involves multiple values.
State updates depend on complex logic.
Unlike useState
, which uses a simple updater function, useReducer
allows you to centralize state updates within a reducer function, making your logic cleaner and easier to maintain.
Syntax
The useReducer
Hook accepts two arguments:
Reducer Function: A function that determines the state changes based on the dispatched action.
Initial State: The initial value of your state, which can be a simple value or an object.
const [state, dispatch] = useReducer(reducer, initialState);
state
: The current state value.dispatch
: A function used to trigger state updates.
Example 1: Simple Counter
Let’s build a simple counter app using useReducer
:
import { useReducer } from "react";
import ReactDOM from "react-dom/client";
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
case "RESET":
return { count: 0 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>Count: {state.count}</h1>
<button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
<button onClick={() => dispatch({ type: "RESET" })}>Reset</button>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Counter />);
This example illustrates how useReducer
simplifies managing multiple state transitions (increment, decrement, and reset) in a single function.
Example 2: Managing a Shopping Cart
Now, let’s create a shopping cart application to demonstrate a more practical use case:
import { useReducer } from "react";
import ReactDOM from "react-dom/client";
const initialCart = [];
function cartReducer(state, action) {
switch (action.type) {
case "ADD_ITEM":
return [...state, { id: action.id, name: action.name, quantity: 1 }];
case "REMOVE_ITEM":
return state.filter((item) => item.id !== action.id);
case "INCREASE_QUANTITY":
return state.map((item) =>
item.id === action.id
? { ...item, quantity: item.quantity + 1 }
: item
);
case "DECREASE_QUANTITY":
return state.map((item) =>
item.id === action.id && item.quantity > 1
? { ...item, quantity: item.quantity - 1 }
: item
);
default:
return state;
}
}
function ShoppingCart() {
const [cart, dispatch] = useReducer(cartReducer, initialCart);
const addItem = (id, name) => {
dispatch({ type: "ADD_ITEM", id, name });
};
return (
<div>
<button onClick={() => addItem(1, "Apple")}>Add Apple</button>
<button onClick={() => addItem(2, "Banana")}>Add Banana</button>
<h2>Shopping Cart:</h2>
{cart.map((item) => (
<div key={item.id}>
<p>
{item.name} (x{item.quantity})
<button onClick={() => dispatch({ type: "INCREASE_QUANTITY", id: item.id })}>+</button>
<button onClick={() => dispatch({ type: "DECREASE_QUANTITY", id: item.id })}>-</button>
<button onClick={() => dispatch({ type: "REMOVE_ITEM", id: item.id })}>Remove</button>
</p>
</div>
))}
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<ShoppingCart />);
This example manages multiple actions (add, remove, increase, and decrease quantity) in a centralized cartReducer
.
Why Use useReducer
?
Here are a few reasons to use useReducer
:
Centralized Logic: All state transitions are handled in a single function, improving maintainability.
Scalability: As your app grows, you can easily add more actions without cluttering your component logic.
Predictable Updates: The
reducer
ensures that state updates are consistent and predictable.
Best Practices
Keep Reducer Functions Pure: Avoid side effects like API calls inside the reducer. Use
useEffect
for side effects.Use Descriptive Action Types: Clearly name your action types to make the reducer logic self-explanatory.
Group Related States: Use objects for initial state when managing multiple related values.
Conclusion
The useReducer
Hook is a powerful tool for managing state logic in React, especially for applications with complex or interdependent state transitions. By mastering useReducer
, you can write more maintainable and scalable React applications.
Experiment with the examples above to see how useReducer
can simplify your state management workflow!
Source Code
The complete source code for this part is available on GitHub