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