


State Management with useContext and useReducer in React: Building a Global Shopping Cart
Nov 11, 2024 pm 02:38 PMAdvanced State Management with useContext and useReducer in React: Building a Global Shopping Cart
In the previous article, we introduced the concept of combining useContext and useReducer to manage global state effectively in a React application. We demonstrated this by building a simple to-do list. Now, we’re going to take things up a notch and apply these concepts to a more complex, real-world example—a global shopping cart.
This guide will cover how to manage multiple states and actions, such as adding, updating, and removing items, and calculating totals—all while keeping the application scalable and performant.
In this second part, you’ll learn to:
- Handle more complex states using useReducer.
- Create a flexible context provider to manage state and actions globally.
- Implement advanced reducer functions to perform calculations and handle various types of actions.
- Optimize component performance with memoization for better performance.
Let's dive in!
Project Overview: A Global Shopping Cart
Our shopping cart application will have:
- Product List: A set of items available to add to the cart.
- Cart Functionality: Users can add, update, and remove items in the cart.
- Cart Totals: Calculate and display the total items and the total price.
We’ll start by setting up the context and reducer, then build components to showcase the features.
Setup and Initial Files
To get started, initialize your React project and set up a basic folder structure:
src/ ├── CartContext.js ├── CartProvider.js ├── ProductList.js ├── Cart.js └── App.js
Step 1: Create the Initial State and Reducer
We’ll start with an initial state that represents an empty cart and a set of sample products.
Initial State:
// Initial state structure const initialState = { products: [ { id: 1, name: "Product A", price: 30 }, { id: 2, name: "Product B", price: 20 }, { id: 3, name: "Product C", price: 50 } ], cart: [], totalItems: 0, totalPrice: 0 };
Reducer Function:
We’ll set up a cartReducer function to handle various actions such as adding items, updating item quantities, removing items, and calculating totals.
src/ ├── CartContext.js ├── CartProvider.js ├── ProductList.js ├── Cart.js └── App.js
Explanation
- ADD_TO_CART: Adds an item to the cart, increasing the quantity if it already exists.
- REMOVE_FROM_CART: Removes an item based on its ID.
- UPDATE_QUANTITY: Updates the quantity of an item in the cart.
- CALCULATE_TOTALS: Calculates the total number of items and the total price of the cart.
Step 2: Create Context and Provider
Now, we’ll create a context and provider to pass our state and dispatch function globally. This will allow all components to access the cart state and actions.
CartContext.js
// Initial state structure const initialState = { products: [ { id: 1, name: "Product A", price: 30 }, { id: 2, name: "Product B", price: 20 }, { id: 3, name: "Product C", price: 50 } ], cart: [], totalItems: 0, totalPrice: 0 };
Step 3: Building the Components
With the provider and context set up, we can now create components for the Product List and the Cart.
ProductList Component
The ProductList component will display a list of available products and allow users to add products to the cart.
ProductList.js
function cartReducer(state, action) { switch (action.type) { case "ADD_TO_CART": { const item = state.cart.find(item => item.id === action.payload.id); const updatedCart = item ? state.cart.map(cartItem => cartItem.id === item.id ? { ...cartItem, quantity: cartItem.quantity + 1 } : cartItem ) : [...state.cart, { ...action.payload, quantity: 1 }]; return { ...state, cart: updatedCart }; } case "REMOVE_FROM_CART": { const updatedCart = state.cart.filter(item => item.id !== action.payload); return { ...state, cart: updatedCart }; } case "UPDATE_QUANTITY": { const updatedCart = state.cart.map(item => item.id === action.payload.id ? { ...item, quantity: action.payload.quantity } : item ); return { ...state, cart: updatedCart }; } case "CALCULATE_TOTALS": { const { totalItems, totalPrice } = state.cart.reduce( (totals, item) => { totals.totalItems += item.quantity; totals.totalPrice += item.price * item.quantity; return totals; }, { totalItems: 0, totalPrice: 0 } ); return { ...state, totalItems, totalPrice }; } default: return state; } }
Cart Component
The Cart component displays items in the cart, allows updating quantities, removing items, and shows the total items and price.
Cart.js
import React, { createContext, useReducer } from 'react'; export const CartContext = createContext(); export function CartProvider({ children }) { const [state, dispatch] = useReducer(cartReducer, initialState); return ( <CartContext.Provider value={{ state, dispatch }}> {children} </CartContext.Provider> ); }
Explanation
- handleRemove: Removes an item from the cart.
- handleUpdateQuantity: Updates the quantity of a selected item.
- Total Items and Price: The cart component displays the total items and price based on the calculated values in the state.
Step 4: Wrap the App with the Provider
To ensure all components can access the cart state, wrap the entire app in CartProvider.
App.js
import React, { useContext } from 'react'; import { CartContext } from './CartContext'; function ProductList() { const { state, dispatch } = useContext(CartContext); const handleAddToCart = (product) => { dispatch({ type: "ADD_TO_CART", payload: product }); dispatch({ type: "CALCULATE_TOTALS" }); }; return ( <div> <h2>Products</h2> <ul> {state.products.map(product => ( <li key={product.id}> {product.name} - ${product.price} <button onClick={() => handleAddToCart(product)}>Add to Cart</button> </li> ))} </ul> </div> ); } export default ProductList;
Final Touches: Memoization and Optimization
As your application grows, optimizing performance is essential. Here are a few tips:
- Memoize Components: Use React.memo to prevent unnecessary re-renders of components that depend on state.
- Separate Contexts: Consider separating product and cart contexts if they become too large, allowing more targeted state updates.
Recap and Conclusion
In this advanced guide, we used useContext and useReducer to manage a global shopping cart. Key takeaways include:
- Complex State Management: useReducer simplifies managing complex actions and calculations.
- Global State with useContext: Makes state accessible across the component tree.
- Scalable Patterns: Separating state and actions in contexts allows for cleaner, more modular code.
Try applying this approach to your projects, and see how it improves the scalability and performance of your applications. Happy coding! ?
The above is the detailed content of State Management with useContext and useReducer in React: Building a Global Shopping Cart. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

JavaScript's garbage collection mechanism automatically manages memory through a tag-clearing algorithm to reduce the risk of memory leakage. The engine traverses and marks the active object from the root object, and unmarked is treated as garbage and cleared. For example, when the object is no longer referenced (such as setting the variable to null), it will be released in the next round of recycling. Common causes of memory leaks include: ① Uncleared timers or event listeners; ② References to external variables in closures; ③ Global variables continue to hold a large amount of data. The V8 engine optimizes recycling efficiency through strategies such as generational recycling, incremental marking, parallel/concurrent recycling, and reduces the main thread blocking time. During development, unnecessary global references should be avoided and object associations should be promptly decorated to improve performance and stability.

There are three common ways to initiate HTTP requests in Node.js: use built-in modules, axios, and node-fetch. 1. Use the built-in http/https module without dependencies, which is suitable for basic scenarios, but requires manual processing of data stitching and error monitoring, such as using https.get() to obtain data or send POST requests through .write(); 2.axios is a third-party library based on Promise. It has concise syntax and powerful functions, supports async/await, automatic JSON conversion, interceptor, etc. It is recommended to simplify asynchronous request operations; 3.node-fetch provides a style similar to browser fetch, based on Promise and simple syntax

JavaScript data types are divided into primitive types and reference types. Primitive types include string, number, boolean, null, undefined, and symbol. The values are immutable and copies are copied when assigning values, so they do not affect each other; reference types such as objects, arrays and functions store memory addresses, and variables pointing to the same object will affect each other. Typeof and instanceof can be used to determine types, but pay attention to the historical issues of typeofnull. Understanding these two types of differences can help write more stable and reliable code.

Which JavaScript framework is the best choice? The answer is to choose the most suitable one according to your needs. 1.React is flexible and free, suitable for medium and large projects that require high customization and team architecture capabilities; 2. Angular provides complete solutions, suitable for enterprise-level applications and long-term maintenance; 3. Vue is easy to use, suitable for small and medium-sized projects or rapid development. In addition, whether there is an existing technology stack, team size, project life cycle and whether SSR is needed are also important factors in choosing a framework. In short, there is no absolutely the best framework, the best choice is the one that suits your needs.

Hello, JavaScript developers! Welcome to this week's JavaScript news! This week we will focus on: Oracle's trademark dispute with Deno, new JavaScript time objects are supported by browsers, Google Chrome updates, and some powerful developer tools. Let's get started! Oracle's trademark dispute with Deno Oracle's attempt to register a "JavaScript" trademark has caused controversy. Ryan Dahl, the creator of Node.js and Deno, has filed a petition to cancel the trademark, and he believes that JavaScript is an open standard and should not be used by Oracle

IIFE (ImmediatelyInvokedFunctionExpression) is a function expression executed immediately after definition, used to isolate variables and avoid contaminating global scope. It is called by wrapping the function in parentheses to make it an expression and a pair of brackets immediately followed by it, such as (function(){/code/})();. Its core uses include: 1. Avoid variable conflicts and prevent duplication of naming between multiple scripts; 2. Create a private scope to make the internal variables invisible; 3. Modular code to facilitate initialization without exposing too many variables. Common writing methods include versions passed with parameters and versions of ES6 arrow function, but note that expressions and ties must be used.

Promise is the core mechanism for handling asynchronous operations in JavaScript. Understanding chain calls, error handling and combiners is the key to mastering their applications. 1. The chain call returns a new Promise through .then() to realize asynchronous process concatenation. Each .then() receives the previous result and can return a value or a Promise; 2. Error handling should use .catch() to catch exceptions to avoid silent failures, and can return the default value in catch to continue the process; 3. Combinators such as Promise.all() (successfully successful only after all success), Promise.race() (the first completion is returned) and Promise.allSettled() (waiting for all completions)

CacheAPI is a tool provided by the browser to cache network requests, which is often used in conjunction with ServiceWorker to improve website performance and offline experience. 1. It allows developers to manually store resources such as scripts, style sheets, pictures, etc.; 2. It can match cache responses according to requests; 3. It supports deleting specific caches or clearing the entire cache; 4. It can implement cache priority or network priority strategies through ServiceWorker listening to fetch events; 5. It is often used for offline support, speed up repeated access speed, preloading key resources and background update content; 6. When using it, you need to pay attention to cache version control, storage restrictions and the difference from HTTP caching mechanism.
