Skip to content
Hou - Engineer & Tech Educator

Using useReducer to Manage Complex State Changes in React

React, useReducer2 min read

What is useReducer?

useReducer is a hook in React that provides a way to manage more complex state in your components. It's an alternative to using useState for state management and allows you to handle state changes in a more structured way.

Why/When to use useReducer?

useReducer is useful when you have more complex state that involves multiple values or more complex data structures. For example, if you have an app that manages multiple forms with different fields, you could use useReducer to manage the state of each form and its fields.

useReducer is also useful when you have logic that depends on previous state or multiple pieces of state. By using a reducer function, you can more easily manage the flow of data and logic in your application. For example, consider a shopping cart application where you need to manage a list of items and their quantities. In this case, using useState to manage each item's quantity would quickly become cumbersome, and a more structured approach like useReducer would be more appropriate.

How to use useReducer?

To use useReducer, you first need to define an initial state object that contains all the values you need to manage. You'll also need to define a reducer function that will handle updates to the state. Here's an example:

1import React, { useReducer } from "react";
2
3const initialState = {
4 items: [],
5 total: 0,
6};
7
8const cartReducer = (state, action) => {
9 switch (action.type) {
10 case "ADD_ITEM":
11 const newItem = action.payload;
12 const existingItem = state.items.find((item) => item.id === newItem.id);
13
14 if (existingItem) {
15 existingItem.quantity += newItem.quantity;
16 } else {
17 state.items.push(newItem);
18 }
19
20 state.total += newItem.quantity * newItem.price;
21
22 return { ...state };
23
24 case "REMOVE_ITEM":
25 const itemId = action.payload;
26 const itemToRemove = state.items.find((item) => item.id === itemId);
27
28 if (itemToRemove) {
29 state.items = state.items.filter((item) => item.id !== itemId);
30 state.total -= itemToRemove.quantity * itemToRemove.price;
31 }
32
33 return { ...state };
34
35 default:
36 return state;
37 }
38};
39
40const Cart = () => {
41 const [cart, dispatch] = useReducer(cartReducer, initialState);
42
43 return (
44 <div>
45 <h1>Shopping Cart</h1>
46
47 <ul>
48 {cart.items.map((item) => (
49 <li key={item.id}>
50 {item.name} - {item.quantity} x ${item.price} = $
51 {item.quantity * item.price}
52 <button
53 onClick={() =>
54 dispatch({ type: "REMOVE_ITEM", payload: item.id })
55 }
56 >
57 Remove
58 </button>
59 </li>
60 ))}
61 </ul>
62
63 <h2>Total: ${cart.total}</h2>
64
65 <button
66 onClick={() =>
67 dispatch({
68 type: "ADD_ITEM",
69 payload: { id: 1, name: "Item 1", price: 10, quantity: 1 },
70 })
71 }
72 >
73 Add Item
74 </button>
75 </div>
76 );
77};
78
79export default Cart;

In this example, we define an initial state object with two properties: an array of items and a total price. We also define a reducer function that handles two actions: adding an item to the cart and removing an item from the cart.

When an item is added to the cart, the reducer checks if the item already exists in the cart. If it does, it increments the quantity of the existing item. If not, it adds the new item to the cart array. The reducer also updates the total price based on the added item.

When an item is removed from the cart, the reducer removes it from the cart array and updates the total price accordingly.

In the Cart component, we use useReducer to manage the state of the cart. We display the list of items in the cart and their quantities, and we also display the total price. We use the dispatch function to trigger the ADD_ITEM and REMOVE_ITEM actions when the user clicks on the "Add Item" or "Remove" buttons.

How to organize reducer functions

When working on a larger-scale React project, it's important to organize your reducer functions in a way that makes them easy to manage and maintain. My preferred approach is to group reducers by feature because I find that it makes it easier to locate and modify the relevant code when working on a specific feature. For example, if you have a shopping cart feature and a user profile feature, you can have separate reducer functions for each feature.

1src/
2 features/
3 cart/
4 cartReducer.js
5 cartActions.js
6 user/
7 userReducer.js
8 userActions.js

Review

In this blog post, we explored the useReducer hook in React and saw how it can be used to manage more complex state in your components. We looked at an example of a shopping cart application where useReducer was used to manage a list of items and their quantities. We defined an initial state object and a reducer function that handled two actions: adding an item to the cart and removing an item from the cart. We also saw how to use the dispatch function to trigger these actions. Overall, useReducer provides a structured approach to state management and is a great alternative to using useState when dealing with more complex state in your components.