On this page
React Shopping Cart
Build an e-commerce cart interface with React. Practice components, props, state, and lists.
Requirements
- Node.js 18+ and npm
- React basics — setup, JSX, hooks
- Create React App or Vite (
npm create vite@latest cart-app -- --template react)
Features
- Product catalog with name, price, and image
- Add products to cart
- Adjust quantity and remove items
- Display subtotal and item count
- Empty cart state
Step 1: Sample Product Data
src/data/products.js
export const products = [
{ id: 1, name: 'Wireless Headphones', price: 79.99, image: '🎧' },
{ id: 2, name: 'Mechanical Keyboard', price: 129.99, image: '⌨️' },
{ id: 3, name: 'USB-C Hub', price: 49.99, image: '🔌' },
{ id: 4, name: 'Monitor Stand', price: 39.99, image: '🖥️' },
];
Step 2: ProductList Component
src/components/ProductList.jsx
export default function ProductList({ products, onAdd }) {
return (
<div className="product-grid">
{products.map(product => (
<div key={product.id} className="product-card">
<span className="product-icon">{product.image}</span>
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
<button onClick={() => onAdd(product)}>Add to Cart</button>
</div>
))}
</div>
);
}
Step 3: Cart State in App
src/App.jsx
import { useState } from 'react';
import { products } from './data/products';
import ProductList from './components/ProductList';
import Cart from './components/Cart';
export default function App() {
const [cartItems, setCartItems] = useState([]);
function addToCart(product) {
setCartItems(prev => {
const existing = prev.find(item => item.id === product.id);
if (existing) {
return prev.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...prev, { ...product, quantity: 1 }];
});
}
return (
<div className="app">
<h1>Shop</h1>
<ProductList products={products} onAdd={addToCart} />
<Cart items={cartItems} setItems={setCartItems} />
</div>
);
}
Step 4: Cart Component
src/components/Cart.jsx
export default function Cart({ items, setItems }) {
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const count = items.reduce((sum, item) => sum + item.quantity, 0);
function updateQuantity(id, delta) {
setItems(prev =>
prev
.map(item =>
item.id === id
? { ...item, quantity: item.quantity + delta }
: item
)
.filter(item => item.quantity > 0)
);
}
function removeItem(id) {
setItems(prev => prev.filter(item => item.id !== id));
}
if (items.length === 0) {
return <div className="cart empty">Your cart is empty</div>;
}
return (
<div className="cart">
<h2>Cart ({count} items)</h2>
{items.map(item => (
<div key={item.id} className="cart-item">
<span>{item.image} {item.name}</span>
<div className="quantity-controls">
<button onClick={() => updateQuantity(item.id, -1)}>−</button>
<span>{item.quantity}</span>
<button onClick={() => updateQuantity(item.id, 1)}>+</button>
</div>
<span>${(item.price * item.quantity).toFixed(2)}</span>
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
<p className="total">Total: ${total.toFixed(2)}</p>
</div>
);
}
Step 5: Basic Styling
Add to src/App.css:
.app { max-width: 900px; margin: 0 auto; padding: 2rem; font-family: system-ui, sans-serif; }
.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; }
.product-card { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; text-align: center; }
.product-icon { font-size: 2rem; }
.cart { margin-top: 2rem; border-top: 2px solid #eee; padding-top: 1rem; }
.cart-item { display: flex; align-items: center; gap: 1rem; padding: 0.5rem 0; }
.total { font-size: 1.25rem; font-weight: bold; text-align: right; }
Extension Ideas
- Checkout flow — multi-step form with shipping and payment summary
- localStorage persistence — save cart between sessions with
useEffect - Product search and filters — filter by price range or category
- Discount codes — apply percentage or fixed discounts
- Stock limits — disable “Add” when quantity exceeds available stock
- Unit tests — test cart logic with React Testing Library