Business Scenario
Hello talented developers!
In the previous lab, Lab 8 — ShopKart Product Details & Dynamic Routing, you enhanced the ShopKart application by allowing users to navigate from the Product Page to individual Product Details pages. Users could view detailed product information, specifications, reviews, select quantities, and add products to the cart.
As the ShopKart product catalog grows, displaying a large number of products on a single page can make it difficult for users to find and compare products. To improve product discovery and navigation, Lab 9 will enhance the existing Product Page by introducing sorting and pagination.
In this lab, students will implement sorting options that allow users to organize products based on criteria such as price, rating, and popularity.
Pagination will divide the products into multiple pages and display only a specific number of products on each page.
Next-Lab Preparation
Module:
1) useState , Array Methods
2) Event handling , Conditional Rendering, List Rendering
git pull origin branchNameGit Pull
Task 1 : Add Sorting Functionality
Create Sorting State
1
const [sortOption, setSortOption] = useState("");useState if it is not already imported and inside the Products component, addAdd the Sort Dropdown
2
<div className="sort-section">
<label htmlFor="sort">Sort By:</label>
<select id="sort" value={sortOption} onChange={(e) => setSortOption(e.target.value)}>
<option value="">Default</option>
<option value="price-low">Price: Low to High</option>
<option value="price-high">Price: High to Low</option>
<option value="rating-high">Rating: High to Low</option>
<option value="rating-low">Rating: Low to High</option>
<option value="popular">Most Popular</option>
</select>
</div>Create a Copy of Filtered Products
3
sort().filteredProducts logic, add:let sortedProducts = [...filteredProducts];Implement Price Sorting
4
sort() method to arrange products according to their price.if (sortOption === "price-low") {
sortedProducts.sort(
(a, b) =>
Number(String(a.price).replace(/,/g, "")) -
Number(String(b.price).replace(/,/g, ""))
);
}
if (sortOption === "price-high") {
sortedProducts.sort(
(a, b) =>
Number(String(b.price).replace(/,/g, "")) -
Number(String(a.price).replace(/,/g, ""))
);
}.sort((a, b) => ...) compares two products at a time.String() and .replace() remove commas from prices like "12,499".Number() converts the price into a number for proper comparison.a - b sorts low to high, while b - a sorts high to low.Implement Rating Sorting
5
if (sortOption === "rating-high") {
sortedProducts.sort( (a, b) => b.rating - a.rating );
}
if (sortOption === "rating-low") {
sortedProducts.sort( (a, b) => a.rating - b.rating );
}Implement Rating Sorting
6
if (sortOption === "popular") {
sortedProducts.sort(
(a, b) => b.reviews - a.reviews
);
}Display the Sorted List
7
filteredProducts.map() with sortedProducts.map() so the Product Page displays the products according to the selected sorting option.filteredProducts.map((product) => (sortedProducts.map((product) => (sortedProducts.length > 0Style the Sort drop-down
8
Task 2 : Add Pagination
const product = productDetails.find(
(item) => item.id === Number(id)
);Handle an Invalid Product ID
8
useParams() gives the product ID from the URL as a string, such as "3".3.Number(id) converts "3" into the number 3..find() searches the productDetails array for the product whose ID matchesproduct, which we then use to display its details.ProductDetails.jsx, immediately after the previous code add : if (!product) {
return (
<main className="product-not-found">
<h1>Product Not Found</h1>
<p> The product you are looking for does not exist. </p>
<Link to="/products">
Back to Products
</Link>
</main>
);
}Add the Dynamic Route in App.jsx
9
/products/:id should open ProductDetails.jsx.<Route
path="/products/:id"
element={
<ProductDetails dispatch={dispatch} />
}
/>Test the Route Before Building the UI
10
ProductDetails.jsx, after the product check, add:return (
<main>
<h1>{product.name}</h1>
</main>
);Open: /products/1
Try opening a product ID that is not present in the product details data.
Task 2 : Create the Complete Product Details UI
return (
<main>
<h1>{product.name}</h1>
</main>
);
with the complete Product Details page.
Add Product Overview
1
<section className="product-overview">
<div className="product-image-section">
<img src={product.image} alt={product.name} />
</div>
<div className="product-info-section">
<p className="product-category">{product.category}</p>
<h1>{product.name}</h1>
<p className="product-brand">Brand: {product.brand}</p>
<div className="product-rating">
⭐ {product.rating}
<span>({product.reviews} reviews)</span>
</div>
<p className="product-short-description">{product.shortDescription}</p>
</div>
</section>Add Pricing
2
product-info-section div, after the short description.<div className="product-pricing">
<span className="current-price">₹{product.price.toLocaleString("en-IN")}</span>
<span className="original-price">₹{product.originalPrice.toLocaleString("en-IN")}
</span>
<span className="discount">{product.discount} OFF</span>
</div><div className="product-availability">
<strong> {product.availability} </strong>
<p> Free delivery available on eligible orders. </p>
</div><div className="purchase-section">
<div className="quantity-section">
<span>Quantity</span>
<div className="quantity-control">
<button type="button" onClick={() =>
setQuantity((previous) => Math.max(1, previous - 1))}>−</button>
<span>{quantity}</span>
<button type="button" onClick={() =>
setQuantity((previous) => previous + 1)}>+</button>
</div>
</div>
<div className="purchase-buttons">
<button className="add-cart-btn" onClick={handleAddToCart}>Add to Cart</button>
<button className="buy-now-btn" onClick={handleBuyNow}>Buy Now</button>
</div>
</div>Add Quantity and Purchase Buttons
3
const handleAddToCart = () => {
for (let i = 0; i < quantity; i++) {
dispatch({
type: "ADD_ITEM",
payload: product
});
}
};Create the Add to Cart Handler
4
if (!product) block, add:Create the Buy Now Handler
5
const handleBuyNow = () => {
for (let i = 0; i < quantity; i++) {
dispatch({
type: "ADD_ITEM",
payload: product
});
}
navigate("/cart");
};Add Product Description
6
</section> of .product-overview, add:<section className="product-description-section">
<h2>Product Description</h2>
<p> {product.description} </p>
</section>Add Key Features
7
<section className="product-features-section">
<h2>Key Features</h2>
<ul>
{product.keyFeatures.map(
(feature, index) => (
<li key={index}> {feature} </li>
)
)} </ul>
</section>Add Specifications
8
<section className="specifications-section">
<h2>Specifications</h2>
<div className="specifications-grid">
{Object.entries(product.specifications).map(([key, value]) => (
<div key={key}>
<strong>{formatKey(key)}</strong>
<span>{value}</span>
</div>
))}
</div>
</section>Add FormatKey()
9
const formatKey = (key) => {
return key
.replace(/([A-Z])/g, " $1")
.replace(/^./, (letter) =>
letter.toUpperCase()
);
}; Object.entries() is used to convert the product.specifications object into an array of key-value pairs, so we can easily loop through it using .map().
Add Customer reviews
10
<section className="product-reviews-section">
<h2>Customer Reviews</h2>
<div className="reviews-list">
{product.customerReviews.map((review, index) => (
<article className="review-card" key={index}>
<h3>{review.name}</h3>
<p className="review-rating">⭐ {review.rating}/5</p>
<p>{review.comment}</p>
</article>
))}
</div>
</section>wheelType
Wheel Type
Style the entire product details page
10
Task 2 : Make Product Cards Open Product Details
belong to the product selected by the user.
User clicks Samsung Galaxy M14 5G
↓
Product ID = 1
↓
/products/1
↓
Product Details page opens
↓
Samsung Galaxy M14 5G details are displayed
Open Products.jsx At the top, find your existing imports add:
1
import { useNavigate } from "react-router-dom"; const navigate = useNavigate();
Add the Dynamic Link
2
.map().<div className="products-grid">
{filteredProducts.length > 0 ? (
filteredProducts.map((product) => (
<div className="product-card" key={product.id}>
.....
....<div className="products-grid">
{filteredProducts.length > 0 ? (
filteredProducts.map((product) => (
<div className="product-card" key={product.id}>
<div
className="product-image"
onClick={() => navigate(`/products/${product.id}`)} >
{product.discount && (
<span className="discount-badge"> -{product.discount} </span>
)} <button className="wishlist-button" onClick={(e) => e.stopPropagation()} >
♡
</button>
<img src={product.image} alt={product.name} />
</div>
<div className="product-info" onClick={() => navigate(`/products/${product.id}`)}>
<small>{product.category}</small>
<h3>{product.name}</h3>
<div className="rating">
⭐ {product.rating} <span>({product.reviews})</span>
</div>
<div className="product-price">
₹{product.price} <del>₹{product.originalPrice}</del>
</div>
<button
className="add-cart-button"
onClick={(e) => {
e.stopPropagation();
dispatch({
type: "ADD_ITEM",
payload: product
}); setAddedProductId(product.id);
setTimeout(() => {
setAddedProductId(null);
}, 2000);
}}
>
{addedProductId === product.id ? (
<>✓ Added to Cart</>
) : (
<>🛒 Add to Cart</>
)}
</button>
</div>
</div>
))
) : (
<div className="no-products">
<h3>No Products Found !</h3>
<p>Try changing your filters.</p>
</div>
)}
</div>Add Product Card Tooltip on hover
3
<div className="product-card" key={product.id} title="Click for more info">title="Click for more info" to your .product-card:Task 3 : Create Not Found Page for Invalid Routes
/about, /contact, /login, /register
/offer, /shop, /abc123
Create NotFound.jsx in pages folder
1
import React from "react";
import { Link } from "react-router-dom";
import "./NotFound.css";
function NotFound() {
return (
<div className="not-found-page">
<div className="not-found-container">
{/* 404 Illustration */}
<div className="not-found-illustration">
<img src="/images/not-found-cart.png" alt="Shopping cart"
className="not-found-cart" />
</div>
{/* 404 Message */}
<h1>Oops! Page Not Found</h1>
<p className="not-found-description">
Looks like this page went shopping and never came back.
</p>
<p className="not-found-subtext">
The page you're looking for doesn't exist or may have been moved.
</p> {/* Buttons */}
<div className="error-buttons">
<Link to="/" className="home-button">
Back to Home
</Link>
<Link to="/products" className="products-button">
Explore Products
</Link>
</div>
</div>
</div>
);
}
export default NotFound;Style the NotFound page
2
Add NotFound.jsx route in App.jsx
3
<Route path="*" element={<NotFound />} />* is a wildcard* symbol means any path.We are done with this lab. The latest source code has been uploaded to GitHub. You can access the latest commit using the link below:
Great job!
You successfully implemented dynamic product details, routing, cart actions, and a 404 page in ShopKart.
Checkpoint
Git Push
git push origin branchNameNext-Lab Preparation
Module:
1) useState , Array Methods
2) Event handling , Conditional Rendering, List Rendering