Product list
Search products, filter by category, sort them, and split the results into pages of 6.
Product List
Key points
- 1
Don’t change the original data — build just what you show
Starting from all products, apply filter → sort → slice (pagination) to build what appears on screen. The original list stays untouched.
- 2
Go back to page 1 when the filters change
If you search while on page 2, the results might only fill one page, leaving you on an empty page. Whenever the filters change, reset the page to 1.
- 3
Have a screen for “no results”
When nothing matches, show “No products found” with a button to clear the filters. A blank screen looks broken.
Tools used
Click to see the comparison in Part 2
Edit the code
Edit the code and the preview on the right updates.
import { useState } from "react" import { SearchIcon } from "lucide-react" import { Button } from "./components/ui/button" import { Input } from "./components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./components/ui/select" type Product = { id: number name: string category: string price: number rating: number color: string // A color in place of a photo } // Product data (12 items) const products: Product[] = [ { id: 1, name: "Wireless Earbuds", category: "Electronics", price: 85, rating: 4.5, color: "#0ea5e9" }, { id: 2, name: "Steel Bottle", category: "Kitchen", price: 20, rating: 4.2, color: "#10b981" }, { id: 3, name: "Backpack 20L", category: "Fashion", price: 50, rating: 4.7, color: "#f59e0b" }, { id: 4, name: "Desk Lamp", category: "Electronics", price: 40, rating: 4.0, color: "#8b5cf6" }, { id: 5, name: "Coffee Grinder", category: "Kitchen", price: 30, rating: 4.6, color: "#ef4444" }, { id: 6, name: "Sneakers", category: "Fashion", price: 65, rating: 4.3, color: "#ec4899" }, { id: 7, name: "Power Bank", category: "Electronics", price: 26, rating: 4.4, color: "#14b8a6" }, { id: 8, name: "Frying Pan 26cm", category: "Kitchen", price: 23, rating: 4.1, color: "#f97316" }, { id: 9, name: "Cap", category: "Fashion", price: 16, rating: 3.9, color: "#6366f1" }, { id: 10, name: "Electric Kettle", category: "Electronics", price: 33, rating: 4.5, color: "#84cc16" }, { id: 11, name: "Cutting Board", category: "Kitchen", price: 13, rating: 4.0, color: "#a855f7" }, { id: 12, name: "Wristwatch", category: "Fashion", price: 105, rating: 4.8, color: "#64748b" }, ] const categories = ["All", "Electronics", "Kitchen", "Fashion"] // Items per page const pageSize = 6 // A card for one product function ProductCard({ product }: { product: Product }) { return ( <div className="overflow-hidden rounded-lg border bg-card"> <div className="h-24" style={{ backgroundColor: product.color }} /> <div className="p-3"> <p className="text-xs text-muted-foreground">{product.category}</p> <p className="mt-0.5 text-sm font-medium">{product.name}</p> <div className="mt-2 flex items-center justify-between"> <p className="text-sm font-semibold">${product.price.toLocaleString()}</p> <p className="text-xs text-amber-500">★ {product.rating}</p> </div> </div> </div> ) } export default function App() { // ① State the screen remembers const [query, setQuery] = useState("") const [category, setCategory] = useState("All") const [sort, setSort] = useState("recommended") const [page, setPage] = useState(1) // ② Products to show: filter → sort → paginate const filtered = products.filter((product) => product.name.includes(query) && (category === "All" || product.category === category)) // Price: low to high let sorted = filtered if (sort === "cheap") sorted = [...filtered].sort((a, b) => a.price - b.price) // Price: high to low if (sort === "expensive") sorted = [...filtered].sort((a, b) => b.price - a.price) // Highest rated first if (sort === "rating") sorted = [...filtered].sort((a, b) => b.rating - a.rating) // Number of pages, from the filtered count const totalPages = Math.ceil(sorted.length / pageSize) // Page 1 → slice(0, 6), page 2 → slice(6, 12) const shown = sorted.slice((page - 1) * pageSize, page * pageSize) //console.log(query, category, shown) return ( <div className="mx-auto my-6 w-full max-w-3xl rounded-xl border bg-card p-6 text-card-foreground"> {/* Search and sort */} <div className="flex flex-wrap items-center gap-2"> {/* An outline magnifying-glass icon on the left of the input */} <div className="relative flex-1"> <SearchIcon className="absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" /> <Input value={query} onChange={(e) => { setQuery(e.target.value) setPage(1) // Go back to page 1 after searching }} className="pl-9" placeholder="Search products" /> </div> <Select value={sort} onValueChange={(value) => { setSort(value) setPage(1) // Go back to page 1 when the sort changes }}> <SelectTrigger className="w-40"> <SelectValue /> </SelectTrigger> <SelectContent position="popper"> <SelectItem value="recommended">Recommended</SelectItem> <SelectItem value="cheap">Price: low to high</SelectItem> <SelectItem value="expensive">Price: high to low</SelectItem> <SelectItem value="rating">Highest rated</SelectItem> </SelectContent> </Select> </div> {/* Categories */} <div className="mt-3 flex flex-wrap gap-2"> {categories.map((name) => ( <Button key={name} size="sm" variant={ category === name ? "default" : "outline" } onClick={() => { setCategory(name) setPage(1) // Go back to page 1 when the category changes }} className="rounded-full"> {name} </Button> ))} </div> <p className="mt-4 text-xs text-muted-foreground">{sorted.length} {sorted.length === 1 ? "item" : "items"}</p> {/* Product cards */} {sorted.length === 0 ? ( <p className="py-10 text-center text-sm text-muted-foreground">No products found</p> ) : ( <div className="mt-2 grid grid-cols-2 gap-3 sm:grid-cols-3"> {shown.map((product) => ( <ProductCard key={product.id} product={product} /> ))} </div> )} {/* Pagination */} <div className="mt-5 flex items-center justify-center gap-1"> <Button size="sm" variant="outline" disabled={page === 1} onClick={page === 1 ? undefined : () => setPage(page - 1)}> Previous </Button> {Array.from({ length: totalPages }, (_, i) => i + 1).map ((number) => ( <Button key={number} size="sm" variant={page === number ? "default" : "outline"} onClick={() => setPage(number)}> {number} </Button> ))} <Button size="sm" variant="outline" disabled={page >= totalPages} onClick={() => setPage(page + 1)}> Next </Button> </div> </div> ) }