E-commerce Application
Build a complete e-commerce application with product catalog, shopping cart, and checkout functionality. This is a foundational concept in component-based UI development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React experience. Take your time with each section and practice the examples
Project Overview
Create a full-featured e-commerce application with product management, user authentication, shopping cart, and payment integration.. This is an essential concept that every React developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Project Structure
// E-commerce app structure
src/
├── components/
│ ├── layout/
│ │ ├── Header.js
│ │ ├── Footer.js
│ │ └── Navigation.js
│ ├── product/
│ │ ├── ProductCard.js
│ │ ├── ProductList.js
│ │ ├── ProductDetail.js
│ │ └── ProductFilter.js
│ ├── cart/
│ │ ├── CartItem.js
│ │ ├── CartSummary.js
│ │ └── CartDrawer.js
│ ├── auth/
│ │ ├── LoginForm.js
│ │ ├── RegisterForm.js
│ │ └── UserProfile.js
│ └── checkout/
│ ├── CheckoutForm.js
│ ├── OrderSummary.js
│ └── PaymentForm.js
├── pages/
│ ├── Home.js
│ ├── Products.js
│ ├── ProductDetail.js
│ ├── Cart.js
│ ├── Checkout.js
│ ├── Login.js
│ └── Profile.js
├── hooks/
│ ├── useCart.js
│ ├── useAuth.js
│ └── useProducts.js
├── context/
│ ├── CartContext.js
│ ├── AuthContext.js
│ └── ProductContext.js
├── utils/
│ ├── api.js
│ ├── validation.js
│ └── helpers.js
└── styles/
├── globals.css
└── components.cssKey Features Implementation
// Product management with React Query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Product hooks
function useProducts(filters = {}) {
return useQuery({
queryKey: ['products', filters],
queryFn: async () => {
const params = new URLSearchParams(filters);
const response = await fetch(`/api/products?${params}`);
return response.json();
},
});
}
function useProduct(productId) {
return useQuery({
queryKey: ['product', productId],
queryFn: async () => {
const response = await fetch(`/api/products/${productId}`);
return response.json();
},
enabled: !!productId,
});
}
// Shopping cart with Context API
import { createContext, useContext, useReducer } from 'react';
const CartContext = createContext();
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
const existingItem = state.items.find(item => item.id === action.payload.id);
if (existingItem) {
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
return {
...state,
items: [...state.items, { ...action.payload, quantity: 1 }]
};
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
case 'UPDATE_QUANTITY':
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: action.payload.quantity }
: item
)
};
case 'CLEAR_CART':
return { ...state, items: [] };
default:
return state;
}
};
function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, { items: [] });
const addItem = (product) => {
dispatch({ type: 'ADD_ITEM', payload: product });
};
const removeItem = (productId) => {
dispatch({ type: 'REMOVE_ITEM', payload: productId });
};
const updateQuantity = (productId, quantity) => {
dispatch({ type: 'UPDATE_QUANTITY', payload: { id: productId, quantity } });
};
const clearCart = () => {
dispatch({ type: 'CLEAR_CART' });
};
const getTotal = () => {
return state.items.reduce((total, item) => total + (item.price * item.quantity), 0);
};
const getItemCount = () => {
return state.items.reduce((count, item) => count + item.quantity, 0);
};
return (
<CartContext.Provider value={{
items: state.items,
addItem,
removeItem,
updateQuantity,
clearCart,
getTotal,
getItemCount
}}>
{children}
</CartContext.Provider>
);
}
function useCart() {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within a CartProvider');
}
return context;
}
// Product components
function ProductCard({ product }) {
const { addItem } = useCart();
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p className="price">{product.price}</p>
<p className="description">{product.description}</p>
<button onClick={() => addItem(product)}>
Add to Cart
</button>
</div>
);
}
function ProductList({ filters }) {
const { data: products, isLoading, error } = useProducts(filters);
if (isLoading) return <div>Loading products...</div>;
if (error) return <div>Error loading products</div>;
return (
<div className="product-grid">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// Cart components
function CartDrawer({ isOpen, onClose }) {
const { items, removeItem, updateQuantity, getTotal } = useCart();
return (
<div className={`cart-drawer ${isOpen ? 'open' : ''}`}>
<div className="cart-header">
<h2>Shopping Cart</h2>
<button onClick={onClose}>×</button>
</div>
<div className="cart-items">
{items.map(item => (
<CartItem
key={item.id}
item={item}
onRemove={() => removeItem(item.id)}
onUpdateQuantity={(quantity) => updateQuantity(item.id, quantity)}
/>
))}
</div>
<div className="cart-footer">
<div className="cart-total">
<strong>Total: {getTotal().toFixed(2)}</strong>
</div>
<button className="checkout-btn">Proceed to Checkout</button>
</div>
</div>
);
}
function CartItem({ item, onRemove, onUpdateQuantity }) {
return (
<div className="cart-item">
<img src={item.image} alt={item.name} />
<div className="item-details">
<h4>{item.name}</h4>
<p>{item.price}</p>
<div className="quantity-controls">
<button onClick={() => onUpdateQuantity(item.quantity - 1)}>-</button>
<span>{item.quantity}</span>
<button onClick={() => onUpdateQuantity(item.quantity + 1)}>+</button>
</div>
</div>
<button onClick={onRemove} className="remove-btn">Remove</button>
</div>
);
}