Building a Shopping Cart App with React and Redux— Chapter 3

Al Amin Alif
6 min readMar 25, 2023

--

In previous two chapters we have learnt and used local state, reducers, dispatching actions, useSelector hooks etc. In this post, I’m going to walk you through the process of building a simple e-commerce web application using React and Redux. The application allows users to add products to their cart and update the cart accordingly. You can check out the live site here:

https://effortless-paletas-09e8a6.netlify.app/

Overview: The application is built using React, Redux, and tailwind css for styling. The main functionality of the app is to enable users to add products to their cart and update the cart accordingly. When a user adds a product to their cart, the product is added to a local Redux store, and the cart is updated to display the added product. The cart also displays the total price of all the items in the cart.

Here is the github repo link : https://rb.gy/qh00hh

Folder Structure: Before we dive into the code, let’s take a look at the folder structure of the project:

  • src/components: This folder contains the React components that make up the UI of the app, including the Product component, which displays a single product and allows the user to add it to their cart, and the Cart component, which displays the current items in the cart and allows the user to update or remove them.
  • src/reducers: This folder contains the cartReducer.js file, which defines the structure of the cart state in the Redux store and handles actions related to adding, updating, or removing items from the cart.
  • src/actions: This folder contains action creators that define the actions that can be dispatched to the Redux store, such as addToCart and removeFromCart.

Code Walkthrough: Now let’s take a look at the code behind the e-commerce web application. Starting with the Product component, we can see that it renders a single product card with an "Add to Cart" button:

import { useDispatch } from "react-redux";
import { addToCart } from "../../redux/products/actions";


export default function Product({ product }) {
const { productName, productCategory, id, price, quantity, imgUrl } = product;

const dispatch = useDispatch();
const handleAddToCart = (id) => {
event.preventDefault();
dispatch(addToCart(id));
}

return (
<div className="lws-productCard">
<img className="lws-productImage" src={imgUrl} alt="product" />
<div className="p-4 space-y-2">
<h4 className="lws-productName">{productName}</h4>
<p className="lws-productCategory">{productCategory}</p>
<div className="flex items-center justify-between pb-2">
<p className="productPrice">BDT <span className="lws-price">{price}</span></p>
<p className="productQuantity">QTY <span className="lws-quantity">{quantity}</span></p>
</div>
<button className="lws-btnAddToCart" onClick={(event) => handleAddToCart(id)}>Add To Cart</button>
</div>
</div>
)
}

When the “Add to Cart” button is clicked, the addToCart action creator is called with the product data:

const dispatch = useDispatch();
const handleAddToCart = (id) => {
event.preventDefault();
dispatch(addToCart(id));
}

The addToCart action creator dispatches an action to the Redux store with the product data:

The cartReducer.js file in the reducers folder handles this action and updates the cart state in the Redux store:

import { ADDPRODUCT, ADDTOCART } from "./actionTypes"
import { INCREASE, DECREASE, DELETE } from "../carts/actionTypes"

const products = [];

const nextProductId = (products) => {
const maxId = products.reduce((maxId, product) => Math.max(product.id, maxId), -1);
return maxId + 1;
}
const reducer = (state = products, action) => {
switch (action.type) {
case ADDPRODUCT:
console.log(products);
let product = action.payload;
return [...state, { ...product, id: nextProductId(state), initialQuantity: parseInt(product.quantity), inCart: 0 }];

case ADDTOCART:
let productId = action.payload;
console.log("add to cart in id: ", productId);
return state.map((el) => {
if (el.id === productId) {
const { inCart, initialQuantity } = el;
const newQuantity = Math.max(el.quantity - 1, 0);
const newInCart = Math.min(inCart + 1, initialQuantity);
return { ...el, inCart: newInCart, quantity: newQuantity }
}
return el;
});
case INCREASE:
let productIdIncrease = action.payload;
return state.map((el) => {
if (el.id === productIdIncrease) {
const { inCart, initialQuantity } = el;
const newQuantity = Math.max(el.quantity - 1, 0);
const newInCart = Math.min(inCart + 1, initialQuantity);
return { ...el, inCart: newInCart, quantity: newQuantity }
}
return el;
});
case DECREASE:
let productIdDecrease = action.payload;
return state.map((el) => {
if (el.id === productIdDecrease) {
const { inCart, initialQuantity } = el;
const newQuantity = Math.min(el.quantity + 1, initialQuantity);
const newInCart = Math.max(inCart - 1, 0);
return { ...el, inCart: newInCart, quantity: newQuantity }
}
return el;
});
case DELETE:
let productIdDelete = action.payload;
return state.map((el) => {
if (el.id === productIdDelete) {
const { initialQuantity } = el;
const newQuantity = initialQuantity;
const newInCart = 0;
return { ...el, inCart: newInCart, quantity: newQuantity }
}
return el;
});
default:
return products;
}

}
export default reducer;

This is the reducer function that manages the state of products in the Redux store. It handles various action types such as adding a new product, adding a product to the cart, increasing or decreasing the quantity of a product in the cart, and deleting a product from the cart. The reducer function updates the state of products based on the actions dispatched to it. The initial state of the products is an empty array.

Finally, the Cart component in the components folder displays the current items in the cart and allows the user to update or remove them:

import { useDispatch } from "react-redux";
import { increase, decrease, deleteCartItem } from "../../redux/carts/actions";

export const CartItem = ({ product }) => {
const { productName, productCategory, id, price, quantity, imgUrl, inCart } = product;

const dispatch = useDispatch();
const handleincrease = (id) => {
event.preventDefault();
dispatch(increase(id));
}
const handleDecrease = (id) => {
console.log("decreae method called");
event.preventDefault();
dispatch(decrease(id));
}
const handleDelete = (id) => {
event.preventDefault();
dispatch(deleteCartItem(id));
}
return (
<div class="cartCard">
<div class="flex items-center col-span-6 space-x-6">
{/* cart image */}
<img class="lws-cartImage" src={imgUrl} alt="product" />
{/* cart item info */}
<div class="space-y-2">
<h4 class="lws-cartName">{productName}</h4>
<p class="lws-cartCategory">{productCategory}</p>
<p>BDT <span class="lws-cartPrice">{price}</span></p>
</div>
</div>
<div class="flex items-center justify-center col-span-4 mt-4 space-x-8 md:mt-0">
{/* amount buttons */}
<div class="flex items-center space-x-4">
<button class="lws-incrementQuantity" onClick={(event) => handleincrease(product.id)}>
<i class="text-lg fa-solid fa-plus"></i>
</button>
<span class="lws-cartQuantity">{inCart}</span>
<button class="lws-decrementQuantity" onClick={(event) => handleDecrease(product.id)}>
<i class="text-lg fa-solid fa-minus"></i>
</button>
</div>
{/* price */}
<p class="text-lg font-bold">BDT <span class="lws-calculatedPrice">{inCart * price}</span></p>
</div>
{/* delete button */}
<div class="flex items-center justify-center col-span-2 mt-4 md:justify-end md:mt-0">
<button class="lws-removeFromCart" onClick={(event) => handleDelete(product.id)}>
<i class="text-lg text-red-400 fa-solid fa-trash"></i>
</button>
</div>
</div>

)
}

This code exports a functional component named CartItem which renders a single item in the cart. The component receives a prop named product which contains all the details of the product being rendered.

Inside the component, the useDispatch hook is used to create a dispatch function which is used to dispatch actions to the Redux store. The component also defines three functions: handleIncrease, handleDecrease, and handleDelete, which will dispatch the corresponding actions when called.

The JSX code inside the return statement contains the HTML and CSS required to render the cart item. It renders the product image, name, category, price, quantity, calculated price, and buttons to increase, decrease, and delete the item from the cart. When a user clicks on any of these buttons, the corresponding function is called, which dispatches the relevant action to the Redux store.

In this post, we walked through the process of building a simple e-commerce web application using React and Redux. We looked at the folder structure of the project and discussed how the Product component, cartReducer.js, actions, and Cart component work together to allow users to add products to their cart and update the cart accordingly. You can check out the live site here: https://effortless-paletas-09e8a6.netlify.app/

--

--