React30-Project 18: Shopping cart app by utilising useReducer hook
Introduction
In this tutorial elevate your React state management skills with the useReducer hook! In this tutorial, we’ll create a simple Shopping Cart application using React and Bootstrap, demonstrating how useReducer simplifies state updates.
Goals
Build a shopping cart utilising `useReducer` hook as shown below
Explanation of useReducer hook
For beginners:
Imagine you have a robot friend who helps you keep track of your toys. The robot knows the rules for playing with each toy. When you want to do something with a toy, you just tell the robot what you want to do (like play, put away, or share), and the robot takes care of it, making sure everything stays organized.
Technically:
useReducer is a React Hook that manages state in a more controlled way than useState. It takes a reducer function and an initial state. The reducer function specifies how state should change in response to different actions. When you want to update the state, you dispatch an action to the reducer. The reducer then processes the action and returns a new state. This helps to handle more complex state logic and manage state transitions more predictably than using useState alone.
Now let’s continue building the project:
Prerequisites
Ensure you have Node.js and npm installed on your machine. Familiarity with React and Bootstrap basics is assumed.
Implementation
1. Create React App
Start a new React app using Create React App
npx create-react-app shopping-cart
cd shopping-cart
2. Install Bootstrap and react-icons
Install Bootstrap and react-icons for styling
npm install bootstrap react-icons
3. Create CartReducer
Create a cartReducer.js file in the src folder:
// cartReducer.js
const cartReducer = (state, action) => {
switch (action.type) {
case "ADD_ITEM":
return [...state, { id: action.payload.id, name: action.payload.name, price: action.payload.price }];
case "REMOVE_ITEM":
return state.filter((item) => item.id !== action.payload);
default:
return state;
}
};
export default cartReducer;
This reducer is responsible for handling actions and updating store state.
4. Create CartContext
Create a CartContext.js file in the src folder:
import { useReducer, createContext } from "react";
import cartReducer from "./cartReducer";
const CartContext = createContext();
export const CartProvider = ({ children }) => {
const [cart, dispatch] = useReducer(cartReducer, []);
return (
<CartContext.Provider value={{ cart, dispatch }}>
{children}
</CartContext.Provider>
);
};
export default CartContext;
Here we have initialised `cartReducer` utilising useReducer hook and then passing it globally using useContext hook. This allows us to read store state and dispatch actions easily anywhere within the app.
5. Create ProductList Component
Create a ProductList.js component in the src folder:
import React, { useContext } from "react";
import CartContext from "./CartContext";
import { FaBagShopping } from "react-icons/fa6";
const products = [
{ id: 1, name: "Product A", price: 10 },
{ id: 2, name: "Product B", price: 15 },
{ id: 3, name: "Product C", price: 20 }
];
const ProductList = () => {
const { dispatch } = useContext(CartContext);
const handleAddToCart = (product) => {
dispatch({ type: "ADD_ITEM", payload: product });
};
return (
<div className="mt-2">
<h2>
<FaBagShopping className="productlist-icon" />
Products
</h2>
{products.map((product) => (
<div className="card" style={{ width: "18rem" }} key={product.id}>
<div className="card-body">
<h5 className="card-title">{product.name}</h5>
<p className="card-text">Price: {product.price}</p>
<button
className="btn btn-primary"
onClick={() => handleAddToCart(product)}
>
Add to Cart
</button>
</div>
</div>
))}
</div>
);
};
export default ProductList;
Here we display a list of products. When user clicks on `Add To Cart` button we dispatch `ADD_ITEM` action to update cart state.
6. Create ShoppingCart Component
Create a ShoppingCart.js component in the src folder:
// ShoppingCart.js
import { useContext } from "react";
import { FaCartShopping } from "react-icons/fa6";
import CartContext from "./CartContext";
const ShoppingCart = () => {
const { cart, dispatch } = useContext(CartContext);
const handleRemoveFromCart = (itemId) => {
dispatch({ type: "REMOVE_ITEM", payload: itemId });
};
return (
<div>
<h2>
<FaCartShopping />
Shopping Cart
</h2>
{cart.map((item) => (
<div className="card" style={{ width: "18rem" }} key={item.id}>
<div className="card-body">
<h5 className="card-title">{item.name}</h5>
<p className="card-text">Price: {item.price}</p>
<button
className="btn btn-primary"
type="button"
onClick={() => handleRemoveFromCart(item.id)}
>
Remove from Cart
</button>
</div>
</div>
))}
</div>
);
};
export default ShoppingCart;
Here we display items added to cart and allow item removal using `REMOVE_ITEM` action.
7. Create Header component
Create `components/Header.js` file in src folder:
import React from "react";
export default function Header(props) {
return (
<nav className="navbar navbar-primary bg-primary w-100">
<div className="container">
<div className="row m-auto">
<i className="fa fa-hand-pointer-o fa-2x text-white mr-2" />
<div className="text-light h2" data-testid="title">
{props.title}
</div>
</div>
</div>
</nav>
);
}
8. Modify App Component
Modify the App.js component to include the ProductList and ShoppingCart components:
import Header from "./components/Header";
import ProductList from "./ProductList";
import ShoppingCart from "./ShoppingCart";
import { CartProvider } from "./CartContext";
import "bootstrap/dist/css/bootstrap.min.css";
import "./styles.css";
const App = () => {
return (
<CartProvider>
<div className="container w-100">
<Header title="Shopping App" />
<ProductList />
<hr />
<ShoppingCart />
</div>
</CartProvider>
);
};
export default App;
9. Run the App
Start the development server:
npm start
Visit http://localhost:3000 in your browser to see the shopping cart app in action!
Conclusion
This example demonstrates how to manage complex state and actions across multiple components using the useReducer hook and context API. Feel free to expand and enhance it based on your needs. Have a nice day ahead !
Check out my YouTube channel for more content:
Next part in the series for building a practical mini project that incorporates lazy loading using React’s Suspense API.