Skip to Content
Module 7: Routing & Navigation🎯 Mini Project: E-commerce

Module 7.5: Mini Project 6 - E-commerce Storefront

A vibrant, isometric digital storefront banner, featuring product displays, search/filter/cart icons, and users navigating, all in energetic orange and trustworthy blue.

(AI Image Gen): > Prompt: A vibrant, bustling digital storefront viewed from a 3D isometric angle. The shop windows display various products (gadgets, clothes). Floating icons represent core features: a magnifying glass (Search), a funnel (Filter), and a shopping cart overflowing with items. Customers (users) are navigating smooth pathways into the store. Color palette: Energetic Orange and Trustworthy Blue.

🛠️ Image Assets Script

Run the following script in your terminal to create placeholder image files:

mkdir -p images/b07-5 touch images/b07-5/b07-5-banner.png touch images/b07-5/b07-5-sitemap.png touch images/b07-5/b07-5-filter-demo.png

🎯 Learning Objectives

In this project, we’ll synthesize our knowledge of Routing to build a mini online storefront, complete with advanced navigation features.

Key features:

  1. Product Listing: Filter and Sort products, synchronizing the state to the URL (useSearchParams).
  2. Product Detail: Dynamic product detail page (/products/:id).
  3. Shopping Cart: Simple shopping cart page.
  4. Navigation: Menu and breadcrumb navigation system.

1. Project Structure (Sitemap)

A hierarchical sitemap diagram showing routes: Home Page, Shop (leading to Product Detail), Cart, and a protected Checkout route.

(AI Image Gen): > Prompt: A hierarchical sitemap diagram. Root: “Home Page”. Branch 1: “Shop” -> “Product Detail”. Branch 2: “Cart”. Branch 3: “Checkout” (Protected Route). Visual style: Corporate blueprint, clear connection lines.

  • /: Home Page (Banner, Featured Products).
  • /shop: Product Listing Page (with Filter Sidebar).
  • /shop/:id: Detail Page.
  • /cart: Cart.

2. Mock Data & Setup

Create the file src/data.js to simulate a database.

// src/data.ts export interface Product { id: number; name: string; category: string; price: number; image: string; } export const PRODUCTS: Product[] = [ { id: 1, name: 'Mechanical Keyboard', category: 'electronics', price: 120, image: '⌨️' }, { id: 2, name: 'Gaming Mouse', category: 'electronics', price: 60, image: '🖱️' }, { id: 3, name: 'Cotton T-Shirt', category: 'clothing', price: 25, image: '👕' }, { id: 4, name: 'Denim Jeans', category: 'clothing', price: 50, image: '👖' }, { id: 5, name: 'Coffee Mug', category: 'home', price: 15, image: '☕' }, ];

3. Building the Shop Page (Filter Sync URL)

This is the most crucial section, applying your knowledge of useSearchParams.

File: src/pages/Shop.jsx

// src/pages/Shop.tsx import { useSearchParams, Link } from 'react-router-dom'; import { PRODUCTS } from '../data'; export default function Shop() { const [searchParams, setSearchParams] = useSearchParams(); // 1. Đọc filter từ URL const categoryFilter = searchParams.get('category') || 'all'; const sortOrder = searchParams.get('sort') || 'asc'; // 2. Hàm xử lý update URL const updateFilter = (key: string, value: string) => { const newParams = new URLSearchParams(searchParams); if (value === 'all') { newParams.delete(key); } else { newParams.set(key, value); } setSearchParams(newParams); }; // 3. Logic lọc và sắp xếp const filteredProducts = PRODUCTS.filter((p) => categoryFilter === 'all' ? true : p.category === categoryFilter ).sort((a, b) => (sortOrder === 'asc' ? a.price - b.price : b.price - a.price)); return ( <div className="flex gap-8 p-8"> {/* Sidebar Controls */} <aside className="w-64 space-y-6"> <div> <h3 className="font-bold mb-2">Category</h3> <div className="flex flex-col gap-2"> {['all', 'electronics', 'clothing', 'home'].map((cat) => ( <label key={cat} className="flex items-center gap-2 cursor-pointer"> <input type="radio" name="category" checked={categoryFilter === cat} onChange={() => updateFilter('category', cat)} /> <span className="capitalize">{cat}</span> </label> ))} </div> </div> <div> <h3 className="font-bold mb-2">Sort Price</h3> <select value={sortOrder} onChange={(e) => updateFilter('sort', e.target.value)} className="w-full border p-2 rounded" > <option value="asc">Low to High</option> <option value="desc">High to Low</option> </select> </div> </aside> {/* Product Grid */} <main className="flex-1"> <h2 className="text-2xl font-bold mb-4">Products ({filteredProducts.length})</h2> <div className="grid grid-cols-3 gap-6"> {filteredProducts.map((product) => ( <div key={product.id} className="border p-4 rounded-xl shadow hover:shadow-lg transition"> <div className="text-4xl mb-4 text-center">{product.image}</div> <h3 className="font-bold text-lg">{product.name}</h3> <p className="text-gray-500 capitalize">{product.category}</p> <div className="flex justify-between items-center mt-4"> <span className="text-green-600 font-bold text-xl">${product.price}</span> <Link to={`/shop/${product.id}`} className="bg-blue-600 text-white px-4 py-2 rounded text-sm hover:bg-blue-700" > View </Link> </div> </div> ))} </div> {filteredProducts.length === 0 && ( <div className="text-center text-gray-400 py-10">No products found matching your filters.</div> )} </main> </div> ); }

A demonstration of URL synchronization: the top half shows the browser URL with category and sort parameters, while the bottom half displays the filtered product grid reflecting those parameters.

(AI Image Gen): > Prompt: A split screen. Top half: The Browser URL bar showing .../shop?category=electronics&sort=desc. Bottom half: The Product Grid showing only electronic gadgets sorted by price. Connecting lines show the relationship between the URL parameters and the visible UI elements.

4. Building the Product Detail Page

This page uses useParams to fetch the ID and useNavigate to create a Back button.

File: src/pages/ProductDetail.jsx

// src/pages/ProductDetail.tsx import { useParams, useNavigate } from 'react-router-dom'; import { PRODUCTS } from '../data'; export default function ProductDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); // Find the product (Note: URL parameters are always strings) const product = PRODUCTS.find((p) => p.id === parseInt(id || '0')); if (!product) { return <div className="p-8 text-center text-red-500">Product not found!</div>; } return ( <div className="max-w-4xl mx-auto p-8"> <button onClick={() => navigate(-1)} // Go back to the previous page className="mb-4 text-gray-500 hover:text-black" > ← Back </button> <div className="grid grid-cols-2 gap-10"> <div className="bg-gray-100 rounded-2xl flex items-center justify-center text-9xl h-96">{product.image}</div> <div> <span className="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm font-bold uppercase tracking-wide"> {product.category} </span> <h1 className="text-4xl font-bold mt-4 mb-2">{product.name}</h1> <p className="text-3xl text-green-600 font-bold mb-6">${product.price}</p> <p className="text-gray-600 mb-8 leading-relaxed"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p> <button onClick={() => alert(`Added ${product.name} to cart!`)} className="w-full bg-black text-white py-4 rounded-xl font-bold text-lg hover:bg-gray-800 transition" > Add to Cart </button> </div> </div> </div> ); }

5. Routing Config (App.jsx)

Finally, let’s assemble everything in App.jsx.

// App.tsx import { Routes, Route, NavLink } from 'react-router-dom'; import Shop from './pages/Shop'; import ProductDetail from './pages/ProductDetail'; // Simple Layout const Navbar = () => ( <nav className="flex gap-6 p-4 border-b justify-center"> <NavLink to="/" className={({ isActive }) => (isActive ? 'font-bold' : '')}> Home </NavLink> <NavLink to="/shop" className={({ isActive }) => (isActive ? 'font-bold' : '')}> Shop </NavLink> <NavLink to="/cart" className={({ isActive }) => (isActive ? 'font-bold' : '')}> Cart </NavLink> </nav> ); export default function App() { return ( <div> <Navbar /> <Routes> <Route path="/" element={<h1 className="text-center mt-10 text-3xl">Welcome to React Store! 🛍️</h1>} /> <Route path="/shop" element={<Shop />} /> <Route path="/shop/:id" element={<ProductDetail />} /> <Route path="/cart" element={<h1 className="text-center mt-10">Cart Page (Coming Soon)</h1>} /> <Route path="*" element={<h1 className="text-center mt-10 text-red-500">404 Not Found</h1>} /> </Routes> </div> ); }

6. Summary

This project helped you practice:

  1. URL Sync: Synchronizing Category and Sort Price filters to the URL, ensuring the state persists on refresh.
  2. Dynamic Routing: Handling product detail pages based on ID.
  3. Navigation: Using useNavigate for backward navigation (-1).

7. Challenges (Optional)

  1. Add a Search feature to the Shop page (synchronizing the search keyword to the URL ?q=...).
  2. Create a CartContext to store the actual shopping cart and display the item count on the Navbar.
Last updated on