
Welcome to the ultimate guide for building a core feature of any modern web application: a fully functional shopping cart!
In the world of e-commerce, the shopping cart is more than just a listβit’s the critical hub where user interaction meets application state. As React developers, understanding how to manage this dynamic state is fundamental.
This tutorial dives into a clean, component-driven approach to building a shopping cart using React Hooks (useState). You’ll learn essential techniques like:
-
State Immortality: Updating arrays correctly in React.
-
Prop Drilling: Passing functions and data down through components.
-
Component Composition: Structuring your application into reusable pieces (
ProductCard,ProductList, andCart).
By the end of this guide, you won’t just have a working cart; you’ll have mastered the state management principles needed to build any complex feature in a modern React application.
Let’s dive in and start coding!
ποΈ Project Structure and Setup
We need four files in total for this improved structure:
src/
βββ components/
β βββ Cart.js
β βββ ProductList.js
β βββ ProductCard.js <-- NEW COMPONENT
βββ data.js
βββ App.js
1. The Data (data.js)
Our list of products remains the same:
// data.js
export const products = [
{ id: 1, name: 'Laptop', price: 1200 },
{ id: 2, name: 'Mouse', price: 25 },
{ id: 3, name: 'Keyboard', price: 75 },
{ id: 4, name: 'Monitor', price: 300 },
];
π¦ Presentation Components
2. Product Card (components/ProductCard.js)
This component handles the display of a single product and provides the button to interact with the cart.
// components/ProductCard.js
import React from 'react';
const ProductCard = ({ product, onAddToCart }) => {
return (
<div
style={{ border: '1px solid #eee', padding: '15px', borderRadius: '5px' }}
>
<h3>{product.name}</h3>
<p>**${product.price.toFixed(2)}**</p>
{/* The button triggers the onAddToCart function passed down from App.js */}
<button
onClick={() => onAddToCart(product)}
style={{ padding: '10px', backgroundColor: 'teal', color: 'white', border: 'none', cursor: 'pointer' }}
>
Add to Cart
</button>
</div>
);
};
export default ProductCard;
3. Product List (components/ProductList.js)
This component is the container. It receives the list of products and the onAddToCart function as props, and its sole job is to map over the list and render a ProductCard for each item.
// components/ProductList.js
import React from 'react';
import ProductCard from './ProductCard'; // <-- Import the Card
const ProductList = ({ products, onAddToCart }) => {
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '20px' }}>
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={onAddToCart}
/>
))}
</div>
);
};
export default ProductList;
4. Cart Display (components/Cart.js)
This component remains the same, responsible for displaying the items and the total.
// components/Cart.js
import React from 'react';
const Cart = ({ items, total, onRemoveFromCart }) => {
return (
<div>
{/* ... (Cart rendering logic remains the same) ... */}
{items.length === 0 ? (
<p>Your cart is empty.</p>
) : (
<>
<ul style={{ listStyle: 'none', padding: 0 }}>
{items.map(item => (
<li
key={item.id}
style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '10px', borderBottom: '1px dotted #ccc', paddingBottom: '5px' }}
>
<span>{item.name} (x{item.quantity})</span>
<span>
**${(item.price * item.quantity).toFixed(2)}**
<button
onClick={() => onRemoveFromCart(item.id)}
style={{ marginLeft: '10px', backgroundColor: 'red', color: 'white', border: 'none', cursor: 'pointer', padding: '5px 8px' }}
>
-
</button>
</span>
</li>
))}
</ul>
<hr />
<h3>Total: **${total.toFixed(2)}**</h3>
</>
)}
</div>
);
};
export default Cart;
βοΈ The Main Application Logic (App.js)
This file is the central hub for state management and passing down the necessary props (data and functions) to its children.
// App.js
import React, { useState } from 'react';
import { products } from './data';
import ProductList from './components/ProductList'; // <-- Imported
import Cart from './components/Cart';
function App() {
const [cartItems, setCartItems] = useState([]);
// Function to add an item to the cart (Logic remains the same)
const addToCart = (product) => {
const existingItem = cartItems.find(item => item.id === product.id);
if (existingItem) {
setCartItems(
cartItems.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
)
);
} else {
setCartItems([...cartItems, { ...product, quantity: 1 }]);
}
};
// Function to remove an item or decrease quantity (Logic remains the same)
const removeFromCart = (productId) => {
setCartItems(
cartItems.reduce((acc, item) => {
if (item.id === productId) {
if (item.quantity > 1) {
acc.push({ ...item, quantity: item.quantity - 1 });
}
} else {
acc.push(item);
}
return acc;
}, [])
);
};
// Calculate the total cost
const cartTotal = cartItems.reduce(
(total, item) => total + item.price * item.quantity,
0
);
return (
<div className="App" style={{ padding: '20px' }}>
<h1>βοΈ Simple E-Commerce App</h1>
<div style={{ display: 'flex', gap: '40px' }}>
{/* Product List Section - Uses the ProductList container */}
<section style={{ flex: 2 }}>
<h2>Products</h2>
<ProductList
products={products}
onAddToCart={addToCart} // <-- Passing the function down
/>
</section>
{/* Cart Section */}
<section style={{ flex: 1, borderLeft: '1px solid #ccc', paddingLeft: '20px' }}>
<h2>Your Cart</h2>
<Cart
items={cartItems}
total={cartTotal}
onRemoveFromCart={removeFromCart}
/>
</section>
</div>
</div>
);
}
export default App;
β Benefits of Component Separation
By splitting the product display into two components, we achieve better React practices:
-
ProductList: Responsible for listing (the structure and mapping). -
ProductCard: Responsible for presenting a single item’s details and handling its specific action (the button).
This makes it easy to change the appearance of a product card without touching the listing logic, and vice versa.
Useful links below:
Let me & my team build you a money making website/blog for your businessΒ https://bit.ly/tnrwebsite_service
Get Bluehost hosting for as little as $1.99/month (save 75%)β¦https://bit.ly/3C1fZd2
Best email marketing automation solution on the market!Β http://www.aweber.com/?373860
Build high converting sales funnels with a few simple clicks of your mouse!Β https://bit.ly/484YV29
Join my Patreon for one-on-one coaching and help with your codingβ¦https://www.patreon.com/c/TyronneRatcliff
Buy me a coffeeΒ https://buymeacoffee.com/tyronneratcliff



