Product list

Search products, filter by category, sort them, and split the results into pages of 6.

Product List

Key points

  1. 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. 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. 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>
  )
}