Add Search to the Product List
Shoppers asked for a way to find a product without scrolling the whole catalog. This adds a search box above the list that filters by name as you type and highlights the matching text. Results are sorted alphabetically so they're easier to scan.
Mweb/src/catalog/ProductList.tsx1 of 3
Files Changed
web/src/catalog/
web/src/catalog/ProductList.tsx
TypeScript · Click a line number to comment · Tap a line to comment@@ -1,15 +1,30 @@
Added: import { useState } from "react";
import type { Product } from "./types";
import { formatPrice } from "./format";
Added: import { highlight } from "./highlight";
export function ProductList({ products }: { products: Product[] }) {
Added: const [query, setQuery] = useState("");
Added: const visible = products
Added: .sort((a, b) => a.name.localeCompare(b.name))
Added: .filter((p) => p.name.includes(query));
Added:
return (
Removed: <ul className="product-list">
Removed: {products.map((p) => (
Removed: <li key={p.id}>
Removed: <span className="name">{p.name}</span>
Removed: <span className="price">{formatPrice(p.priceCents)}</span>
Removed: </li>
Removed: ))}
Removed: </ul>
Added: <div className="product-search">
Added: <input
Added: type="search"
Added: placeholder="Search products"
Added: value={query}
Added: onChange={(e) => setQuery(e.target.value)}
Added: />
Added: <ul className="product-list">
Added: {visible.map((p, index) => (
Added: <li key={index}>
Added: <span className="name" dangerouslySetInnerHTML={{ __html: highlight(p.name, query) }} />
Added: <span className="price">{formatPrice(p.priceCents)}</span>
Added: </li>
Added: ))}
Added: </ul>
Added: </div>
);
}
web/src/catalog/highlight.ts
TypeScript · Click a line number to comment · Tap a line to comment@@ -0,0 +1,4 @@
Added: export function highlight(text: string, query: string): string {
Added: if (!query) return text;
Added: return text.split(query).join("<mark>" + query + "</mark>");
Added: }
web/src/catalog/ProductList.test.tsx
TypeScript · Click a line number to comment · Tap a line to comment@@ -0,0 +1,15 @@
Added: import { render, screen } from "@testing-library/react";
Added: import userEvent from "@testing-library/user-event";
Added: import { ProductList } from "./ProductList";
Added:
Added: const products = [
Added: { id: "p1", name: "Ceramic Mug", priceCents: 1800 },
Added: { id: "p2", name: "Steel Water Bottle", priceCents: 2400 },
Added: ];
Added:
Added: it("filters products by name", async () => {
Added: render(<ProductList products={products} />);
Added: await userEvent.type(screen.getByRole("searchbox"), "Mug");
Added: expect(screen.getAllByRole("listitem")).toHaveLength(1);
Added: expect(screen.getByText("Mug").tagName).toBe("MARK");
Added: });