This React example is a very good start of building a React shopping cart. It combines two major parts of an eCommerce UI. Those are 1) a product gallery and 2) a client-side shopping cart with a checkout option.
After reading this tutorial, you will know how to code the following in a React App.

This lightweight shopping cart supports add, delete and quantity update including increment and decrement features.
The products and cart are managed on the client-side, whereas the checkout includes database intervention. Once checkout the order is placed by moving the selected item to a database entry.
The database is linked with the checkout process. This is to give a lead to move everything based on a database-driven management, if you want.
The product gallery includes a navbar header to show the category menu. It classifies the available products by grouping them into categories.
This example includes 4 sample categories. The selected category is set using the React useState to load classified product cards.
The Products.jsx file sets the product data array and the required React useState. It returns the product cards markup by iterating the data array.
Each product card has a button HTML with a custom hook addToCart().
src/components/Products.jsx
import React, { useState, useEffect } from "react";
import "./Product.css";
const productData = [
{ id: 1, name: "Grapes", category: "Fruits", price: 4.99, image: "img/fruite-item-5.jpg" },
{ id: 2, name: "Raspberries", category: "Fruits", price: 4.99, image: "img/fruite-item-2.jpg" },
{ id: 3, name: "Apple", category: "Fruits", price: 4.99, image: "img/fruite-item-6.jpg" },
{ id: 4, name: "Banana", category: "Fruits", price: 4.99, image: "img/fruite-item-3.jpg" },
{ id: 5, name: "Bread", category: "Bread", price: 5.99, image: "img/bread-item.jpg" },
{ id: 6, name: "Meat", category: "Meat", price: 4.99, image: "img/meat-item.jpg" },
{ id: 7, name: "Broccoli", category: "Vegetables", price: 4.99, image: "img/vegetable-item-2.jpg" },
{ id: 8, name: "Tomato", category: "Vegetables", price: 5.99, image: "img/vegetable-item-1.jpg" }
];
const Product = () => {
const [loadingId, setLoadingId] = useState(null);
const [addedId, setAddedId] = useState(null);
const [selectedCategory, setSelectedCategory] = useState("All");
useEffect(() => {
document.title = "React Shopping Cart";
}, []);
const filteredProducts =
selectedCategory === "All"
? productData
: productData.filter((p) => p.category === selectedCategory);
return (
<section className="product-section">
<div className="product-container">
<div className="product-grid">
{filteredProducts.map((product) => (
<div className="product-card" key={product.id}>
<div className="product-image-container">
<img src={product.image} alt={product.name} className="product-image" />
<div className="product-category-tag">{product.category}</div>
</div>
<div className="product-details">
<h4>{product.name}</h4>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
<div className="product-info-row">
<p className="product-price">${product.price.toFixed(2)} / kg</p>
<button
className="add-to-cart-btn"
onClick={() => addToCart(product)}
disabled={addedId === product.id || loadingId === product.id}
style={{
backgroundColor: addedId === product.id ? "#81c408" : undefined,
color: addedId === product.id ? "#fff" : undefined,
}}
>
{loadingId === product.id ? (
<img
src="img/loader.svg"
alt="Loading..."
style={{ width: "20px", height: "20px" }}
/>
) : addedId === product.id ? (
"Added"
) : (
<>
<i className="fa fa-shopping-bag cart-icon"></i> Add to cart
</>
)}
</button>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default Product;
The React shopping cart JSX file manages cart items in the JavaScript localStorage. The React UseEffect reads the cart items from the localStorage on document mount.
The stored items are set into a state variable by using the setCart(). The setCart() updates the state on each add, delete action trigger.
The syncCart function is called on changing the cart item quantity. This will update the cart state and localStorage. It computes the total cart items and update the header badge with the latest cart item count.

src/components/CartPage.jsx
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";
import "./CartPage.css";
const CartPage = () => {
const [cart, setCart] = useState([]);
const [loadingCheckout, setLoadingCheckout] = useState(false);
const navigate = useNavigate();
useEffect(() => {
document.title = "React Shopping Cart - Checkout";
const storedCart = JSON.parse(localStorage.getItem("cart")) || [];
setCart(storedCart);
}, []);
const syncCart = (updatedCart) => {
setCart(updatedCart);
localStorage.setItem("cart", JSON.stringify(updatedCart));
const totalItems = updatedCart.reduce((sum, item) => sum + item.quantity, 0);
window.dispatchEvent(new CustomEvent("cartUpdated", { detail: totalItems }));
};
if (cart.length === 0) {
return (
<h1
style={{
color: "#81c408",
textAlign: "center",
marginTop: "110px",
backgroundColor: "#f7f7f7",
padding: "20px",
borderRadius: "8px",
height: "100%"
}}
>
Your cart is empty!
</h1>
);
}
return (
<div className="cart-page">
<div className="cart-container">
<div className="clear-cart-wrapper">
<button className="clear-cart-btn" onClick={() => {
localStorage.removeItem("cart");
setCart([]);
window.dispatchEvent(new CustomEvent("cartUpdated", { detail: 0 }));
}}><img src="/img/trash.svg" className="img-trash" alt="trash" />
Clear Cart
</button>
</div>
{/* Cart Table */}
<table className="cart-table">
<thead>
<tr>
<th>Products</th>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
<th>Handle</th>
</tr>
</thead>
<tbody>
{cart.map((item) => (
<tr key={item.id}>
<td><img src={item.image} alt={item.name} /></td>
<td>{item.name}</td>
<td>${item.price.toFixed(2)}</td>
<td>
<div className="quantity-control">
<button className="quantity-btn" onClick={() => updateQuantity(item.id, -1)}>
<i className="fa fa-minus"></i>
</button>
<input type="text" className="quantity-input" value={item.quantity} readOnly />
<button className="quantity-btn" onClick={() => updateQuantity(item.id, 1)}>
<i className="fa fa-plus"></i>
</button>
</div>
</td>
<td>${(item.price * item.quantity).toFixed(2)}</td>
<td>
<button className="remove-btn" onClick={() => removeItem(item.id)}>
<i className="fa fa-times"></i>
</button>
</td>
</tr>
))}
</tbody>
</table>
{/* Cart Summary */}
<div className="cart-layout">
<div className="cart-summary">
<h2>Cart Total</h2>
<div className="summary-row">
<h5>Subtotal:</h5>
<p>${subtotal.toFixed(2)}</p>
</div>
<div className="summary-row">
<h5>Shipping:</h5>
<p>${shipping.toFixed(2)}</p>
</div>
<p style={{ textAlign: "right", marginBottom: "1rem" }}>
Shipping to Ukraine
</p>
<div className="summary-total">
<h5>Total:</h5>
<p>${total.toFixed(2)}</p>
</div>
<button
className="checkout-btn"
onClick={checkout}
disabled={loadingCheckout}
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "8px",
}}
>
{loadingCheckout && (
<img
src="img/loader.svg"
alt="Loading..."
style={{ width: "20px", height: "20px" }}
/>
)}
{loadingCheckout ? "Processing..." : "Proceed Checkout"}
</button>
</div>
</div>
</div>
</div>
);
};
export default CartPage;
With the help of the product category menu, browsing the available products is very smooth and effortless.
The product.js script defines the addToCart() function. When adding a new product to the cart, this functions checks if it exists already.
For an existing cart product, this function just updates the quantity. This is to avoid the confusion of having multiple rows with duplicate product details.
When the cart’s useState is updated, a custom event “cartUpdated” is dispatched with the information of the latest cart item count. This event is listened to in the eCommerce header JSX to update the counter badge.
Part of product.js
const addToCart = (product) => {
setLoadingId(product.id); // show spinner
setTimeout(() => {
// Add to localStorage
let cart = JSON.parse(localStorage.getItem("cart")) || [];
const existing = cart.find((item) => item.id === product.id);
if (existing) {
existing.quantity += 1;
} else {
cart.push({ ...product, quantity: 1 });
}
localStorage.setItem("cart", JSON.stringify(cart));
// Update Navbar cart count
const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0);
window.dispatchEvent(new CustomEvent("cartUpdated", { detail: totalItems }));
setLoadingId(null);
setAddedId(product.id);
}, 1000);
};
Part of CartPage.js
const updateQuantity = (id, delta) => {
const updated = cart.map((item) =>
item.id === id ? { ...item, quantity: Math.max(1, item.quantity + delta) } : item
);
syncCart(updated);
};
const removeItem = (id) => {
const updated = cart.filter((item) => item.id !== id);
syncCart(updated);
};
The script below shows the definition of the React shopping cart checkout process. In this function, I connected the PHP endpoint to show how to link with the backend when working with a front-end React app.
This project downloadable contains the bundle for the PHP backend service. This script targets the endpoint URL to raise a request to process the checkout.
I specify the steps to set up this example in your environment at the end of this tutorial. After completing the set up, replace the endpoint path as per your PHP root.
Once the checkout is done, the products are moved to the order database and the front-end cart state will be reset to empty.
Part of CartPage.js
const checkout = async () => {
const cart = JSON.parse(localStorage.getItem("cart")) || [];
if (cart.length === 0) return;
setLoadingCheckout(true); // show spinner
try {
await new Promise((resolve) => setTimeout(resolve, 2000));
const response = await axios.post("http://localhost/react-shopping-cart-api/cart-checkout-api.php", { cart });
const data = response.data;
if (data.success) {
// Clear cart and navigate to Thank You page
localStorage.removeItem("cart");
window.dispatchEvent(new CustomEvent("cartUpdated", { detail: 0 }));
setCart([]);
navigate("/thankyou");
}
} catch (error) {
console.error("Checkout error:", error);
} finally {
setLoadingCheckout(false);
}
};
const subtotal = cart.reduce((acc, item) => acc + item.price * item.quantity, 0);
const shipping = cart.length > 0 ? 3.0 : 0;
const total = subtotal + shipping;
When the user clicks the “Proceed to checkout” button, the following processes will be taken place.

src/components/ThankYou.jsx
import React, { useEffect } from "react";
import "./ThankYou.css";
const ThankYou = () => {
useEffect(() => {
document.title = "React Shopping Cart - Thankyou";
}, []);
return (
<div className="thankyou-container">
<div className="thankyou-box">
<h1 style={{ color: "#81c408" }}>Thank you for shopping with us!</h1>
<h2 style={{ color: "#333" }}>
You have placed your order. Check your email to see the order receipt.
</h2>
</div>
</div>
);
};
export default ThankYou;
The below steps will save your time to make this example working in your environment.
api/react-shopping-cart-api.zip and unzip it into the PHP root.db_react_shopping_cart and import sql/database.sql from react-shopping-cart-api.zipcd react-shopping-cart via CMD or terminal and run npm install.npm run dev to start the server.We have a very basic shopping cart example in React. It covered the major parts of a shopping cart application that is to have a seamless product browsing and purchase via a cart-checkout flow.
There is more space for improvements to enrich this to a full-fledged shopping cart. The category menu can be a type-ahead field to filter the available categories. The thank you page can be enriched with the “Continue Shopping” lead to hook your customers.
References