Build a Product Catalog with React

Build a searchable, filterable product catalog using React components and state.

Introduction

30 Second Summary

Every time you browse an online store and type in the search bar, the product list updates instantly. That seamless filtering experience is built with the same patterns you're about to learn.

In this project, you will build a searchable product catalog app using React that filters items in real-time based on user input. You'll learn how to break a UI into reusable components, pass data between them, and manage interactive state.

What You'll Build

A React application that displays products organized by category, with a search box and checkbox that filter the list instantly as users interact.

By the end of this project, you'll have:

  • A working React app scaffolded with Vite, running locally with instant hot module replacement.
  • Multiple reusable components (SearchBar, ProductTable, ProductRow, ProductCategoryRow) that pass data via props.
  • Interactive state management with useState that filters products in real-time as users type and toggle options.
  • Secret Mission: Add a "sort by price" button that toggles ascending and descending price order.

Are there any prerequisites?

You should be comfortable with basic HTML, CSS, and JavaScript. Familiarity with variables, functions, arrays, and arrow functions is all you need to get started.

Set Up Your Development Environment

Every React app needs a development environment where you can write code, preview changes, and see updates in real time. Without this setup, you'd be editing files blindly with no feedback loop.

In this step, you'll scaffold a brand-new React project using Vite, a fast build tool that gives you instant browser refreshes as you code. You'll also start a local development server so you can see your app live in the browser.

In this step, get ready to:
  • Open your code editor and terminal.
  • Scaffold a new React project with Vite.
  • Start the development server and see the app in your browser.
Open VS Code and your terminal

With Node.js already installed and verified on your machine, you're ready to scaffold the project. First, open your code editor.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • Open the integrated terminal by pressing Ctrl+` (macOS and Windows).

Why the integrated terminal?

The integrated terminal runs inside your editor window, so you can see your code and run commands side by side. You'll be switching between code files and terminal commands throughout this project.

Scaffold your React project with Vite

Vite is a modern build tool that creates a ready-to-use project structure for you. It also runs a development server with hot module replacement, which means your browser updates instantly when you save a file.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your React project by running this command:
npm create vite@latest product-catalog -- --template react

What does this command do?

  • npm create vite@latest downloads and runs the latest version of the Vite project scaffolding tool.
  • product-catalog is the name of your project folder.
  • -- --template react tells Vite to use the React (JavaScript) template, skipping the framework selection prompt.

Seeing a prompt about installing and starting?

Vite may ask if you want to install dependencies and start the dev server automatically. Select No to decline.

You'll install dependencies and start the server manually in the next commands so you understand each step.

  • Move into your new project folder and install the dependencies by running these commands:
cd product-catalog
npm install

What does npm install do?

Your project has a package.json file listing all the libraries it depends on (like React). Running npm install downloads those libraries into a node_modules folder so your code can use them.

  • Confirm the project was created by running:
ls src

You should see files including App.jsx, App.css, main.jsx, and index.css listed in the output.

Command not found or permission error?

  • Make sure you ran cd ~/Desktop before the scaffold command. If the product-catalog folder already exists from a previous attempt, delete it first with rm -rf product-catalog (macOS) or rmdir /s /q product-catalog (Windows), then try again.
  • If npm is not recognized, confirm Node.js is installed by running node --version. You should see 20.19 or higher.

Help me troubleshoot my Vite scaffold command

Why Vite instead of Create React App?

Create React App is deprecated and no longer maintained. React's official documentation recommends Vite for projects focused on learning React fundamentals.

Vite's development server starts in milliseconds and updates the browser instantly when you save. This tight feedback loop makes it ideal for learning.

Start the development server

Now that your project files are in place, you can start the development server to see the app live in your browser.

  • Start the development server by running:
npm run dev

You should see output in your terminal showing a local URL, typically http://localhost:5173.

  • Hold Cmd (macOS) or Ctrl (Windows) and click the URL in your terminal to open it in your browser.

You should see the default Vite + React welcome page with the Vite and React logos and a counter button.

Keep the dev server running

Leave this terminal running for the rest of the project. The dev server watches your files and refreshes the browser automatically whenever you save a change. If you need to run other commands later, open a new terminal tab with Cmd+Shift+` (macOS) or Ctrl+Shift+` (Windows).

Browser showing a blank page or error?

  • Check your terminal for error messages. If it says the port is in use, stop any other dev servers and try npm run dev again.
  • Make sure you ran npm install before starting the server. Without it, the dependencies are missing and the server will fail.
  • Try opening http://localhost:5173 manually in your browser if clicking the link didn't work.

Help me debug my Vite dev server

Your development environment is ready to go. Next up, you'll replace the default welcome page with your own product catalog components.

Create Your Product Data and Static Components

Your development environment is ready and the Vite dev server is running. Now it's time to build the actual product catalog.

Every React app starts with data and a plan for how to display it. In this step, you'll define your product data, then break the UI into small, reusable components that each handle one job.

In this step, get ready to:
  • Create a product data file with items organized by category.
  • Build table components that render products grouped by category.
  • Wire up a parent component that passes data to the table.
Create the product data file

Before building any UI, you need data to display. You'll store your product catalog in a separate file so the data stays independent from the components that render it.

  • In VS Code's Explorer sidebar, right-click on the src folder.
  • Select New File and name it data.js.
  • Paste the following code into data.js:
const PRODUCTS = [
  { category: "Fruits", price: "$1.00", stocked: true, name: "Apple" },
  { category: "Fruits", price: "$1.50", stocked: true, name: "Dragonfruit" },
  { category: "Fruits", price: "$2.00", stocked: false, name: "Passionfruit" },
  { category: "Vegetables", price: "$2.50", stocked: true, name: "Spinach" },
  { category: "Vegetables", price: "$3.50", stocked: true, name: "Pumpkin" },
  { category: "Vegetables", price: "$4.00", stocked: false, name: "Peas" },
];

export default PRODUCTS;

What does this code do?

This file defines an array of 6 product objects, each with four properties:

  • category groups products together (Fruits or Vegetables).
  • price is a string like "$1.00".
  • stocked is a boolean indicating whether the item is currently available.
  • name is the display name of the product.

The export default at the bottom lets other files import this array.

  • Save src/data.js by pressing Cmd+S (macOS) or Ctrl+S (Windows).

✔️ Awesome, I've got everything!

Great. Make sure you've saved the file before moving on.

ⓧ I'd like to double check the full code

const PRODUCTS = [
  { category: "Fruits", price: "$1.00", stocked: true, name: "Apple" },
  { category: "Fruits", price: "$1.50", stocked: true, name: "Dragonfruit" },
  { category: "Fruits", price: "$2.00", stocked: false, name: "Passionfruit" },
  { category: "Vegetables", price: "$2.50", stocked: true, name: "Spinach" },
  { category: "Vegetables", price: "$3.50", stocked: true, name: "Pumpkin" },
  { category: "Vegetables", price: "$4.00", stocked: false, name: "Peas" },
];

export default PRODUCTS;
Build the table components

React's core idea is breaking a UI into small, focused components. Your product table needs three components: one for category headers, one for individual product rows, and one that assembles them into a full table.

  • In the Explorer sidebar, right-click on the src folder.
  • Select New Folder and name it components.
  • Right-click on the new components folder, select New File, and name it ProductTable.jsx.
  • Paste the following code into ProductTable.jsx:
function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2" className="category-row">
        {category}
      </th>
    </tr>
  );
}

function ProductRow({ product }) {
  // Display out-of-stock products with a red style
  const name = product.stocked ? (
    product.name
  ) : (
    <span className="out-of-stock">{product.name}</span>
  );

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({ products, filterText, inStockOnly }) {
  const rows = [];
  let lastCategory = null;

  // Loop through products and build rows
  products.forEach((product) => {
    // Skip products that don't match the filter text
    if (product.name.toLowerCase().indexOf(filterText.toLowerCase()) === -1) {
      return;
    }
    // Skip out-of-stock products if the checkbox is on
    if (inStockOnly && !product.stocked) {
      return;
    }
    // Add a category header when the category changes
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category}
        />
      );
    }
    rows.push(<ProductRow product={product} key={product.name} />);
    lastCategory = product.category;
  });

  // Count how many products are currently shown
  const totalProducts = products.length;
  const shownProducts = rows.filter(
    (row) => row.type === ProductRow
  ).length;

  return (
    <div>
      <table className="product-table">
        <thead>
          <tr>
            <th>Name</th>
            <th>Price</th>
          </tr>
        </thead>
        <tbody>{rows}</tbody>
      </table>
      <p className="product-count">
        Showing {shownProducts} of {totalProducts} products
      </p>
    </div>
  );
}

export default ProductTable;

What does this code do?

This file defines three components that work together to render a product table:

  • ProductCategoryRow renders a bold header row whenever the category changes (like "Fruits" or "Vegetables").
  • ProductRow renders a single product's name and price. If the product is out of stock, the name gets wrapped in a span with a class for red styling.
  • ProductTable loops through the products array, skipping any that don't match the filter criteria. It groups products by category and displays a count of shown vs total products.

The filterText and inStockOnly props are accepted now but won't actively filter anything yet. You'll connect them to user input in the next step.

  • Save src/components/ProductTable.jsx.

Seeing a red underline in VS Code?

If you see a squiggly line under the JSX (the HTML-like syntax), make sure you named the file with a .jsx extension, not .js. Vite uses the extension to know which files contain JSX.

help me fix JSX errors in my ProductTable component

✔️ Awesome, I've got everything!

Great. Make sure you've saved the file before moving on.

ⓧ I'd like to double check the full code

function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2" className="category-row">
        {category}
      </th>
    </tr>
  );
}

function ProductRow({ product }) {
  const name = product.stocked ? (
    product.name
  ) : (
    <span className="out-of-stock">{product.name}</span>
  );

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({ products, filterText, inStockOnly }) {
  const rows = [];
  let lastCategory = null;

  products.forEach((product) => {
    if (product.name.toLowerCase().indexOf(filterText.toLowerCase()) === -1) {
      return;
    }
    if (inStockOnly && !product.stocked) {
      return;
    }
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category}
        />
      );
    }
    rows.push(<ProductRow product={product} key={product.name} />);
    lastCategory = product.category;
  });

  const totalProducts = products.length;
  const shownProducts = rows.filter(
    (row) => row.type === ProductRow
  ).length;

  return (
    <div>
      <table className="product-table">
        <thead>
          <tr>
            <th>Name</th>
            <th>Price</th>
          </tr>
        </thead>
        <tbody>{rows}</tbody>
      </table>
      <p className="product-count">
        Showing {shownProducts} of {totalProducts} products
      </p>
    </div>
  );
}

export default ProductTable;

Now you need a parent component that imports the data and passes it to ProductTable.

  • Open src/App.jsx in the editor (double-click it in the Explorer sidebar).
  • Select all the existing code and delete it.
  • Paste the following code into App.jsx:
import ProductTable from "./components/ProductTable";
import PRODUCTS from "./data";
import "./App.css";

// Parent component that holds the product data
function FilterableProductTable({ products }) {
  return (
    <div className="filterable-product-table">
      <h1>Product Catalog</h1>
      <ProductTable
        products={products}
        filterText=""
        inStockOnly={false}
      />
    </div>
  );
}

function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

export default App;

What does this code do?

FilterableProductTable is the parent component that owns the product data. It passes the PRODUCTS array down to ProductTable as a prop.

Right now, filterText is hardcoded to an empty string and inStockOnly is hardcoded to false. This means all products show up without any filtering. In the next step, you'll replace these hardcoded values with real state that responds to user input.

  • Save src/App.jsx.
  • Switch to your browser. The page should automatically update thanks to Vite's hot reload.

Seeing a blank page or error?

If your browser shows an error overlay, check these common causes:

  • Make sure src/data.js exists and is saved. The import in App.jsx will fail if the file is missing.
  • Confirm ProductTable.jsx is inside the src/components/ folder, not directly in src/.
  • Check that the file extension is .jsx (not .js) for both component files.

help me debug my React import errors

✔️ Awesome, I've got everything!

Great. Double check you've saved all three files.

ⓧ I'd like to double check the full code

import ProductTable from "./components/ProductTable";
import PRODUCTS from "./data";
import "./App.css";

function FilterableProductTable({ products }) {
  return (
    <div className="filterable-product-table">
      <h1>Product Catalog</h1>
      <ProductTable
        products={products}
        filterText=""
        inStockOnly={false}
      />
    </div>
  );
}

function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

export default App;
Verify the catalog in your browser
  • Switch to your browser tab showing http://localhost:5173.

You should see a heading that says "Product Catalog" followed by a table with two columns: Name and Price. The products are grouped under bold category headers (Fruits and Vegetables).

You should also see "Showing 6 of 6 products" below the table.

Why don't out-of-stock items look different yet?

The code adds an out-of-stock class to items like Passionfruit and Peas, but there's no CSS rule targeting that class yet. You'll add styling in a later step to make these items display in red.

Your static product catalog is displaying correctly. Next up, you'll add a search bar and wire up state so users can filter products in real-time.

Add the Search Bar and State

Your product catalog now displays a nicely grouped list. The next step is to make it interactive so users can search and filter products in real-time.

React handles interactivity through state. When state changes, React automatically re-renders the components that depend on it. In this step, you'll create a SearchBar component and connect it to the state that already lives in App.jsx.

In this step, get ready to:
  • Create a SearchBar component with a text input and checkbox.
  • Understand how state and props wire SearchBar to the product list.
  • Test real-time filtering by typing and toggling the checkbox.
Create the SearchBar component

The SearchBar needs two inputs: a text field for searching by product name, and a checkbox for hiding out-of-stock items. Each input is a controlled component, meaning React state drives its value rather than the DOM.

  • Create a new file called SearchBar.jsx inside the src/components/ folder.
  • Add the following code to SearchBar.jsx:
function SearchBar({
  filterText,
  inStockOnly,
  onFilterTextChange,
  onInStockOnlyChange,
}) {
  return (
    <form className="search-bar">
      <input
        type="text"
        value={filterText}
        placeholder="Search..."
        onChange={(e) => onFilterTextChange(e.target.value)}
      />
      <label>
        <input
          type="checkbox"
          checked={inStockOnly}
          onChange={(e) => onInStockOnlyChange(e.target.checked)}
        />
        {" "}Only show products in stock
      </label>
    </form>
  );
}

export default SearchBar;

What does this code do?

The SearchBar function accepts four props via destructuring. Two are values (filterText and inStockOnly) that control what the inputs display. Two are callback functions (onFilterTextChange and onInStockOnlyChange) that fire when the user types or clicks.

The text input uses value={filterText} to stay in sync with state. The onChange handler grabs the new value from the event and passes it back up to the parent. The checkbox works the same way, except it reads e.target.checked (a boolean) instead of e.target.value.

  • Save src/components/SearchBar.jsx.
  • Switch to your browser. The compile error should disappear and you should see a text input and a checkbox labeled "Only show products in stock" above your product table.

Still seeing an error?

Make sure the file is named exactly SearchBar.jsx (capital S, capital B) and is saved inside src/components/. The import in App.jsx is case-sensitive.

If you see a blank page with no error, check that the export default SearchBar line is at the bottom of the file.

help me fix my SearchBar import error

Understand the state and props wiring

Your SearchBar is now rendering, but how does typing in it actually filter the products? The answer is lifting state up. The parent component owns the data, and children communicate through props.

  • In VS Code, open src/App.jsx and find these two lines near the top of the FilterableProductTable function:
const [filterText, setFilterText] = useState("");
const [inStockOnly, setInStockOnly] = useState(false);

What is useState?

useState is a React Hook that gives a component memory. It returns a pair: the current value and a function to update it. When you call the updater function, React re-renders the component with the new value.

filterText starts as an empty string (""). inStockOnly starts as false. Every time the user types or clicks the checkbox, the setter function updates the value and React re-renders both SearchBar and ProductTable with the new state.

Notice how FilterableProductTable passes these state values and setters down to both child components:

<SearchBar
  filterText={filterText}
  inStockOnly={inStockOnly}
  onFilterTextChange={setFilterText}
  onInStockOnlyChange={setInStockOnly}
/>
<ProductTable
  products={products}
  filterText={filterText}
  inStockOnly={inStockOnly}
/>

Why does the parent own the state?

Both SearchBar and ProductTable need access to filterText and inStockOnly. SearchBar writes to them (user input) and ProductTable reads from them (filtering). The closest common ancestor that contains both is FilterableProductTable.

This pattern is called lifting state up. It keeps data flowing in one direction: state lives in the parent, flows down as props, and children notify the parent through callbacks when something changes.

Test the real-time filtering

Everything is connected. Time to prove it works.

  • Switch to your browser where the app is running.
  • Type fruit in the search box. The table should instantly hide all vegetables, leaving only Fruits visible.
  • Clear the search box and tick the Only show products in stock checkbox. Passionfruit and Peas (the out-of-stock items) should disappear.
  • Try both together: type p while the checkbox is still checked. Only Pumpkin should remain.

What is happening under the hood?

When you type, SearchBar calls onFilterTextChange with the new text. That triggers setFilterText in the parent, which updates state. React re-renders FilterableProductTable, which passes the new filterText to ProductTable. ProductTable filters the list and only renders matching rows.

This entire cycle happens on every keystroke. React makes it fast by only updating the parts of the DOM that actually changed.

Typing but nothing filters?

Check that SearchBar.jsx uses onChange={(e) => onFilterTextChange(e.target.value)} on the text input. If you wrote onFilterTextChange(e) instead, you're passing the event object rather than the string value.

Also confirm that App.jsx passes onFilterTextChange={setFilterText} to the SearchBar. A misspelled prop name means the callback never fires.

help me debug why my search filter is not working in React

✔️ Awesome, I've got everything!

Great. Double check you've saved src/components/SearchBar.jsx and that your filter is working in the browser.

ⓧ I'd like to double check the full code

Here is the complete code for the two key files in this step.

function SearchBar({
  filterText,
  inStockOnly,
  onFilterTextChange,
  onInStockOnlyChange,
}) {
  return (
    <form className="search-bar">
      <input
        type="text"
        value={filterText}
        placeholder="Search..."
        onChange={(e) => onFilterTextChange(e.target.value)}
      />
      <label>
        <input
          type="checkbox"
          checked={inStockOnly}
          onChange={(e) => onInStockOnlyChange(e.target.checked)}
        />
        {" "}Only show products in stock
      </label>
    </form>
  );
}

export default SearchBar;
import { useState } from "react";
import SearchBar from "./components/SearchBar";
import ProductTable from "./components/ProductTable";
import PRODUCTS from "./data";
import "./App.css";

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState("");
  const [inStockOnly, setInStockOnly] = useState(false);

  return (
    <div className="filterable-product-table">
      <h1>Product Catalog</h1>
      <SearchBar
        filterText={filterText}
        inStockOnly={inStockOnly}
        onFilterTextChange={setFilterText}
        onInStockOnlyChange={setInStockOnly}
      />
      <ProductTable
        products={products}
        filterText={filterText}
        inStockOnly={inStockOnly}
      />
    </div>
  );
}

function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

export default App;

Your catalog is now fully interactive. Users can search by name and hide out-of-stock products instantly. Next up, you'll add styling and a product count display to polish the experience.

Polish and Understand the Data Flow

Your product catalog filters products in real-time, but it still looks like a raw HTML table with no styling.

In this step, you'll make the catalog visually presentable with custom CSS. You'll also install React Developer Tools to peek under the hood and see how data flows from parent to child components through props.

In this step, get ready to:
  • Style the catalog with custom CSS for a clean, centered layout.
  • Inspect the component tree and props with React Developer Tools.
  • Verify the product count updates dynamically as you filter.
Style the catalog with custom CSS

Right now your app uses the default Vite scaffold styles. You'll replace those with styles that center the layout, clean up the search bar, and format the product table.

  • Open src/App.css in your editor.
  • Select all the existing content and delete it.
  • Add the container, heading, and search bar styles by pasting this code:
/* Center the catalog in the viewport */
.filterable-product-table {
  max-width: 400px;
  margin: 40px auto;
  font-family: system-ui, -apple-system, sans-serif;
  padding: 20px;
}

/* Style the page heading */
h1 {
  font-size: 1.5rem;
  margin-bottom: 16px;
  color: #1a1a1a;
}

/* Search bar container spacing */
.search-bar {
  margin-bottom: 16px;
}

/* Text input styling */
.search-bar input[type="text"] {
  display: block;
  width: 100%;
  padding: 8px 12px;
  margin-bottom: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
  box-sizing: border-box;
}

/* Checkbox label alignment */
.search-bar label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.9rem;
  color: #444;
}

What does this code do?

The .filterable-product-table class constrains the catalog to 400px wide and centers it horizontally with margin: 40px auto.

The search bar input stretches to full width with rounded corners. The checkbox label uses flexbox to align the checkbox and text on one line.

  • Below the search bar styles, add the table and element styles:
/* Product table layout */
.product-table {
  width: 100%;
  border-collapse: collapse;
}

/* Table cell spacing and borders */
.product-table th,
.product-table td {
  text-align: left;
  padding: 6px 8px;
  border-bottom: 1px solid #eee;
}

/* Bold header row border */
.product-table thead th {
  border-bottom: 2px solid #333;
  font-size: 0.9rem;
}

/* Category row background */
.category-row {
  background-color: #f5f5f5;
  font-weight: bold;
  padding: 8px;
}

/* Red text for out-of-stock items */
.out-of-stock {
  color: #cc0000;
}

/* Small gray product count text */
.product-count {
  margin-top: 12px;
  font-size: 0.85rem;
  color: #666;
}

What does this code do?

The .product-table uses border-collapse: collapse so cell borders merge into clean single lines instead of doubling up.

Category headers get a light gray background to visually separate product groups. Out-of-stock products display in red (#cc0000) so users can spot unavailable items at a glance.

  • Save src/App.css.
  • Switch to your browser. The page should auto-refresh. You should see a centered catalog with a clean search bar, a neatly formatted table, category headers in gray, and out-of-stock items in red.

Styles not showing up?

  • Make sure you deleted all the original Vite styles before pasting. Leftover rules can override your new styles.
  • Check that import "./App.css" is still present at the top of src/App.jsx. Without this import, Vite won't bundle the stylesheet.
  • Confirm the class names in your CSS match the JSX exactly. For example, className="filterable-product-table" in App.jsx must match .filterable-product-table in your CSS.

Why aren't my CSS styles applying to the product catalog?

✔️ Awesome, I've got everything!

Great. Double check you've saved src/App.css and your browser shows the styled catalog.

ⓧ I'd like to double check the full code

.filterable-product-table {
  max-width: 400px;
  margin: 40px auto;
  font-family: system-ui, -apple-system, sans-serif;
  padding: 20px;
}

h1 {
  font-size: 1.5rem;
  margin-bottom: 16px;
  color: #1a1a1a;
}

.search-bar {
  margin-bottom: 16px;
}

.search-bar input[type="text"] {
  display: block;
  width: 100%;
  padding: 8px 12px;
  margin-bottom: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
  box-sizing: border-box;
}

.search-bar label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.9rem;
  color: #444;
}

.product-table {
  width: 100%;
  border-collapse: collapse;
}

.product-table th,
.product-table td {
  text-align: left;
  padding: 6px 8px;
  border-bottom: 1px solid #eee;
}

.product-table thead th {
  border-bottom: 2px solid #333;
  font-size: 0.9rem;
}

.category-row {
  background-color: #f5f5f5;
  font-weight: bold;
  padding: 8px;
}

.out-of-stock {
  color: #cc0000;
}

.product-count {
  margin-top: 12px;
  font-size: 0.85rem;
  color: #666;
}
Inspect data flow with React Developer Tools

Your browser's built-in Elements tab only shows raw HTML. It has no idea that your app is made of React components passing props to each other. React Developer Tools is a browser extension that adds a Components tab, letting you see the component tree and live prop values.

  • Open your browser (Chrome, Edge, or Firefox).
  • Navigate to the browser's extension store. In Chrome, go to the Chrome Web Store.
  • Search for React Developer Tools.
  • Click Add to Chrome (or the equivalent for your browser), then click Add Extension in the confirmation dialog.
  • Return to your product catalog tab at http://localhost:5173.
  • Open DevTools by pressing Cmd+Option+I (macOS) or Ctrl+Shift+I (Windows).
  • Find the Components tab in the DevTools panel. You may need to click the >> overflow arrow to reveal it.

You should see a component tree starting with App at the top, containing FilterableProductTable, which contains SearchBar and ProductTable.

  • Click on FilterableProductTable in the tree. In the right panel, you'll see its hooks: filterText (empty string) and inStockOnly (false).
  • Click on SearchBar and look at the props panel on the right. You'll see filterText, inStockOnly, onFilterTextChange, and onInStockOnlyChange listed as props.
  • Try editing the filterText value directly in DevTools. Click on the hook value in FilterableProductTable, type Pumpkin, and press Enter. The catalog instantly filters to show only Pumpkin.

What is one-way data flow?

React data always flows in one direction: from parent components down to children through props. FilterableProductTable owns the state and passes it down. Children never modify the parent's state directly.

When you edited filterText in the DevTools, you changed the parent's state. React re-rendered every child that depends on that prop. This is the same flow that happens when a user types in the search box.

Don't see the Components tab?

  • Make sure you're on the http://localhost:5173 tab. React Developer Tools only activates on pages running React.
  • Try reloading the page after installing the extension. Some browsers require a page refresh before the extension activates.
  • Check the >> overflow menu at the top of DevTools. The Components tab is often hidden there if your DevTools panel is narrow.

I installed React Developer Tools but I can't find the Components tab in my browser DevTools. How do I fix this?

Verify the dynamic product count

Your ProductTable component already displays a count below the table showing how many products are visible out of the total. Now that the app is styled, you can confirm it responds correctly to every filter combination.

  • Clear the DevTools filterText value back to an empty string (or clear the search box directly in the app).
  • Look below the product table. You should see Showing 6 of 6 products in small gray text.
  • Type p in the search box. The count should update to Showing 4 of 6 products because four product names contain the letter "p" (Passionfruit, Spinach, Pumpkin, Peas).
  • Check the Only show products in stock checkbox. The count should drop further because out-of-stock items are hidden.

Why does the count update automatically?

Every time filterText or inStockOnly changes, React re-renders ProductTable. The shownProducts variable is recalculated on each render based on the current filter state. There's no manual DOM update needed.

  • Uncheck the checkbox and clear the search box to reset the catalog to its full view.

Count shows 0 of 6?

  • Make sure your ProductTable component uses row.type === ProductRow to count product rows. If you accidentally imported ProductRow from a different file or renamed it, the filter comparison will fail.
  • Check that the rows array is populated before the count calculation runs. The forEach loop and the .filter() call must reference the same rows variable.

My product count always shows 0. How do I debug the shownProducts calculation in ProductTable?

Your catalog is styled, interactive, and you've seen firsthand how React's one-way data flow connects your components. Ready for a bonus challenge? The Secret Mission will have you add sort-by-price functionality with additional state management.

Secret mission

Add Sort by Price

Your catalog filters products, but what about sorting them? Add a "Sort by Price" button that cycles through ascending, descending, and default order. This introduces a third piece of state that works alongside your existing filters.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Vite dev server running at http://localhost:5173.
  • The product-catalog project folder on your Desktop (includes source files and node_modules).

Keep everything running

No action needed. Choose this if you're still actively building or want to experiment with the app further.

  • Your dev server stays running at http://localhost:5173.
  • Your product-catalog folder remains on your Desktop.
  • Node.js remains installed on your machine for future projects.

Pause - I'll come back to this later

Shut down the dev server to free up the terminal, but keep your project files for later.

  • Switch to the terminal where your dev server is running.
  • Press Ctrl+C to stop the Vite dev server.
  • Your product-catalog folder stays on your Desktop. When you want to resume, open a terminal in that folder and run npm run dev again.

Delete - I don't want to use this again

Remove all project files and stop the dev server completely.

  • Switch to the terminal where your dev server is running.
  • Press Ctrl+C to stop the Vite dev server.
  • Delete the project folder by running the appropriate command for your operating system.

macOS

  • Run this command to delete the project folder:
cd ~/Desktop
rm -rf product-catalog

Windows

  • Run this command to delete the project folder:
cd ~\Desktop
rmdir /s /q product-catalog

Nice Work!

Nice Work!

You just built your first interactive React app from scratch. You now have a fully working product catalog with real-time search filtering, stock visibility controls, and a clean component architecture running in your browser.

You've learned how to:

  • Break a UI into reusable components (SearchBar, ProductTable, ProductRow, ProductCategoryRow) and compose them into a working app.
  • Manage interactive state with useState and pass data between components using props to create one-way data flow.
  • Implement real-time filtering with controlled inputs so the UI updates instantly as the user types.
  • Added sort-by-price functionality with additional state management to cycle through ascending and descending order.

Ready to quiz yourself?