Build a Product Showcase Page

Build a React product grid with components, props, state, and event handling.

Introduction

30 Second Summary

Think about any online store you browse. Every product card you scroll past was built by someone who knew how to break a page into small, reusable pieces.

In this project, you will build a product showcase page with React. You will create reusable components, wire them together with props, and add interactivity using state.

What You'll Build

A responsive product showcase page that displays a grid of product cards, each with product details and an interactive favorite button.

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

  • A Vite-powered React project running locally in your browser.
  • A reusable ProductCard component that accepts data through props and manages its own state.
  • Hands-on experience with core React patterns like JSX, event handling, and list rendering that you will use in every future React project.
  • A bonus challenge to add a category filter that lets users filter products dynamically.

Are there any prerequisites?

You should have basic HTML, CSS, and JavaScript knowledge (variables, functions). No prior React experience is needed.

Set Up Your React Project

React projects need a build tool to transform JSX and modern JavaScript into code that browsers can understand. Vite is a fast, lightweight build tool that handles this for you with zero configuration.

In this step, you will create a brand new React project using Vite. By the end, you will have a running app in your browser.

In this step, get ready to:
  • Check that Node.js is installed and up to date.
  • Create a new React project with Vite.
  • Start the development server and see it running in your browser.
Check your Node.js version and create the project

Vite requires Node.js version 20.19 or higher to run. Let's confirm your version before creating the project.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Check your Node.js version by running this command:
node --version

I see a version number

Check that the number is v20.19 or higher (for example, v20.19.0 or v22.12.0).

If your version meets this requirement, you are all set. Move on to the next section below.

  • If your version is lower than 20.19, open https://nodejs.org in your browser.
  • Download the latest LTS version and run the installer.
  • Close and reopen your terminal, then run node --version again to confirm.

Command not found

Node.js is not installed yet. Follow these steps to install it.

  • Open https://nodejs.org in your browser.
  • Download the latest LTS version.
  • Run the installer and follow the prompts to complete the installation.
  • Close and reopen your terminal.
  • Run node --version again to confirm it installed successfully.

Now that Node.js is ready, you can create your React project.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create a new React project called product-showcase by running this command:
  • If npm asks to install the create-vite package, type y and press Enter to continue.
npm create vite@latest product-showcase -- --template react

What does this command do?

  • npm create vite@latest runs the latest version of Vite's project scaffolder.
  • product-showcase is the name of your new project folder.
  • -- --template react tells Vite to use the React template with plain JavaScript.
  • You should see a message that includes Scaffolding project in followed by your project path and Done. in the terminal output.

Seeing an error?

  • Make sure you are connected to the internet. The command downloads the Vite scaffolder from npm.
  • Confirm your Node.js version is 20.19 or higher by running node --version.
  • If you see a permissions error on macOS, try prefixing the command with sudo.

Help me fix an error when running npm create vite.

Why Vite instead of Create React App?

Create React App was the original way to start React projects, but the React team officially deprecated it in February 2025. Vite is faster, requires less configuration, and is now the recommended build tool for learning React.

Install dependencies and start the dev server
  • Navigate into your project folder and install its dependencies by running these commands:
cd product-showcase
npm install

What do these commands do?

  • cd product-showcase moves your terminal into the project folder that Vite just created.
  • npm install reads package.json and downloads all the packages your project needs, including React.
  • You should see a message with added followed by a number of packages in the terminal output.

npm install failed?

  • Make sure you are inside the product-showcase folder. Run pwd (macOS) or cd (Windows) to check your current directory.
  • If you see permission errors on macOS, try running the command again with sudo npm install.

Help me fix npm install errors in my Vite project.

  • Start the development server by running:
npm run dev

What is a dev server?

Vite starts a local development server that serves your app and watches for file changes. Every time you save a file, the browser updates automatically without a manual refresh.

  • Open your browser and go to the URL shown in the terminal output. It is usually http://localhost:5173.

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

Don't see the welcome page?

  • Double check the URL in your terminal. Vite displays the exact address after starting the server.
  • Make sure npm run dev is still running in your terminal. If you closed the terminal or pressed Ctrl+C, the server stops.
  • Try a different browser to rule out any browser-specific issues.

Help me see the Vite welcome page in my browser.

Open the project in VS Code and explore the structure

Keep the terminal with npm run dev running. You will open your project in VS Code to explore and edit files.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the product-showcase folder, then click Open.

You should see the project files appear in VS Code's Explorer sidebar on the left.

What are these files?

Vite generated a full project structure for you. Here are the key files you will work with:

  • src/App.jsx is the main component. This is where you will build your product showcase page.
  • src/main.jsx is the entry point that renders the App component into the page.
  • index.html is the single HTML file that React renders into. It contains a <div id="root"> element where your entire app lives.
  • package.json lists your project's dependencies and available scripts.

Don't see any files in the sidebar?

  • Make sure you opened the product-showcase folder itself, not a parent folder like Desktop.
  • Click the Explorer icon at the top of the left sidebar (it looks like two stacked files) to toggle the file panel.

Help me see my project files in VS Code.

Your React project is up and running. Next up, you will replace the default content with your own product data and page layout.

Create Your Product Data and Clean Up the App

Your React project is running and you've explored the file structure. Now it's time to make it yours.

Every Vite project starts with boilerplate demo code. In this step, you'll replace that boilerplate with your own product data, a clean App component, and custom styles.

In this step, get ready to:
  • Create a product data file with sample products.
  • Clean up App.jsx and build the page header.
  • Replace App.css with base styles and a grid layout.
Create a product data file

Your app needs products to display. Instead of hardcoding product details directly into your components, you'll store them in a separate data file.

  • In VS Code, right-click the src folder in the Explorer sidebar and select New Folder.
  • Name the folder data.
  • Right-click the new data folder and select New File.
  • Name the file products.js.
  • Add the following code to products.js:
// Array of product objects for the showcase page
const products = [
  {
    id: 1,
    name: "Wireless Headphones",
    price: 59.99,
    image: "https://picsum.photos/seed/headphones/300/200",
    category: "Electronics",
    description: "Premium sound quality with noise cancellation."
  },
  {
    id: 2,
    name: "Running Shoes",
    price: 89.99,
    image: "https://picsum.photos/seed/shoes/300/200",
    category: "Sports",
    description: "Lightweight and comfortable for daily runs."
  },
  {
    id: 3,
    name: "Coffee Maker",
    price: 34.99,
    image: "https://picsum.photos/seed/coffee/300/200",
    category: "Home",
    description: "Brew the perfect cup every morning."
  },
  {
    id: 4,
    name: "Backpack",
    price: 45.99,
    image: "https://picsum.photos/seed/backpack/300/200",
    category: "Sports",
    description: "Durable and spacious for all your adventures."
  },
  {
    id: 5,
    name: "Desk Lamp",
    price: 24.99,
    image: "https://picsum.photos/seed/lamp/300/200",
    category: "Home",
    description: "Adjustable brightness for your workspace."
  },
  {
    id: 6,
    name: "Bluetooth Speaker",
    price: 39.99,
    image: "https://picsum.photos/seed/speaker/300/200",
    category: "Electronics",
    description: "Portable speaker with rich bass."
  }
];

// Make the array available for other files to import
export default products;

What does this code do?

  • products is an array of 6 objects. Each object represents one product with an id, name, price, image, category, and description.
  • The image URLs use picsum.photos, a free service that returns consistent placeholder images based on a seed word.
  • export default products makes the array available for other files to import.
  • Save products.js.
  • You won't see a change in the browser yet. The next substep imports this data into your App component.

Seeing a red squiggly line in VS Code?

  • Check that every object in the array ends with a closing } followed by a comma (except the last one).
  • Make sure the array's opening [ and closing ] brackets match.
  • Make sure all string values are wrapped in double quotes.

Help me fix a syntax error in my products.js file.

Awesome, I've got everything!

Double check you've saved products.js before moving on.

I'd like to double check the full code

const products = [
  {
    id: 1,
    name: "Wireless Headphones",
    price: 59.99,
    image: "https://picsum.photos/seed/headphones/300/200",
    category: "Electronics",
    description: "Premium sound quality with noise cancellation."
  },
  {
    id: 2,
    name: "Running Shoes",
    price: 89.99,
    image: "https://picsum.photos/seed/shoes/300/200",
    category: "Sports",
    description: "Lightweight and comfortable for daily runs."
  },
  {
    id: 3,
    name: "Coffee Maker",
    price: 34.99,
    image: "https://picsum.photos/seed/coffee/300/200",
    category: "Home",
    description: "Brew the perfect cup every morning."
  },
  {
    id: 4,
    name: "Backpack",
    price: 45.99,
    image: "https://picsum.photos/seed/backpack/300/200",
    category: "Sports",
    description: "Durable and spacious for all your adventures."
  },
  {
    id: 5,
    name: "Desk Lamp",
    price: 24.99,
    image: "https://picsum.photos/seed/lamp/300/200",
    category: "Home",
    description: "Adjustable brightness for your workspace."
  },
  {
    id: 6,
    name: "Bluetooth Speaker",
    price: 39.99,
    image: "https://picsum.photos/seed/speaker/300/200",
    category: "Electronics",
    description: "Portable speaker with rich bass."
  }
];

export default products;
Clean up App.jsx and build the page header

The default App.jsx contains Vite's demo content with logos and a counter. You'll replace all of it with a clean component that imports your product data and renders a page header.

  • Open src/App.jsx in VS Code.
  • Select all the existing code by pressing Cmd+A (macOS) or Ctrl+A (Windows).
  • Replace it with this code:
import products from './data/products';
import './App.css';

// Main App component that structures the page
function App() {
  return (
    <div className="app">
      <header className="header">
        <h1>Product Showcase</h1>
        <p>Discover our latest collection</p>
      </header>
      {/* Product cards will be rendered here */}
      <main className="product-grid">
      </main>
    </div>
  );
}

export default App;

What does this code do?

  • import products from './data/products' loads the product array you just created. You'll use it in a later step to render product cards.
  • import './App.css' tells Vite to apply the styles from App.css to this component.
  • The App function returns JSX that creates a header with a title and subtitle, plus an empty <main> section where product cards will go.

What is JSX?

JSX is a syntax that lets you write HTML-like code inside JavaScript. It looks like HTML, but it gets compiled into JavaScript function calls under the hood.

Notice className instead of class. That's because class is a reserved word in JavaScript, so JSX uses className instead.

  • Save App.jsx.
  • Check your browser. You should see "Product Showcase" as a heading and "Discover our latest collection" below it.

The styling may look off because you're still using Vite's default CSS. You'll fix that next.

Page is blank or showing an error?

  • Make sure you saved both products.js and App.jsx.
  • Check the terminal running your dev server for error messages. A common issue is an incorrect import path. The import should be './data/products' with a leading ./.
  • Confirm that your products.js file is inside src/data/ and not directly inside src/.

Help me debug my blank React page after replacing App.jsx.

Awesome, I've got everything!

Double check you've saved App.jsx before moving on.

I'd like to double check the full code

import products from './data/products';
import './App.css';

function App() {
  return (
    <div className="app">
      <header className="header">
        <h1>Product Showcase</h1>
        <p>Discover our latest collection</p>
      </header>
      <main className="product-grid">
      </main>
    </div>
  );
}

export default App;
Replace App.css with your base styles

The default App.css contains styles for Vite's demo page. You'll replace them with your own styles that set up a centered layout and a CSS Grid for the product cards.

  • Open src/App.css in VS Code.
  • Select all the existing code by pressing Cmd+A (macOS) or Ctrl+A (Windows).
  • Replace it with this code:
/* Reset default browser margins and padding */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Base body styles */
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background-color: #f0f2f5;
  min-height: 100vh;
}

/* Centered page container */
.app {
  max-width: 1200px;
  margin: 0 auto;
  padding: 40px 20px;
}

/* Page header styles */
.header {
  text-align: center;
  margin-bottom: 40px;
}

.header h1 {
  font-size: 2.5rem;
  color: #1a1a2e;
  margin-bottom: 8px;
}

.header p {
  font-size: 1.1rem;
  color: #666;
}

/* Responsive grid layout for product cards */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
}

What does this code do?

  • The * selector resets margins and padding across all elements, giving you a clean starting point.
  • .app centers the content with a max width of 1200px and adds padding around the edges.
  • .header centers the title text and adds spacing below it.
  • .product-grid uses CSS Grid with auto-fill and minmax(280px, 1fr) to create a responsive grid. Cards automatically wrap to new rows as the browser window shrinks.

What is CSS Grid?

CSS Grid is a layout system that arranges elements into rows and columns. The auto-fill keyword tells the browser to fit as many 280px-wide columns as possible. Each column stretches to fill available space thanks to 1fr.

You won't see the grid in action until you add product cards in the next step. For now, the layout is ready and waiting.

  • Save App.css.
  • Check your browser. You should see "Product Showcase" centered on a light gray background with clean spacing around it.

Background still white?

  • Make sure App.jsx has import './App.css' at the top.
  • Confirm you replaced ALL the old CSS, not just appended to it. Select all with Cmd+A (macOS) or Ctrl+A (Windows) before pasting.

My App.css styles aren't showing up in the browser.

Awesome, I've got everything!

Double check you've saved App.css before moving on.

I'd like to double check the full code

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background-color: #f0f2f5;
  min-height: 100vh;
}

.app {
  max-width: 1200px;
  margin: 0 auto;
  padding: 40px 20px;
}

.header {
  text-align: center;
  margin-bottom: 40px;
}

.header h1 {
  font-size: 2.5rem;
  color: #1a1a2e;
  margin-bottom: 8px;
}

.header p {
  font-size: 1.1rem;
  color: #666;
}

.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
}

Your data is ready and your layout is in place. Next up, you'll build a reusable ProductCard component that brings each product to life.

Build a Reusable ProductCard Component

Your app now has product data and a clean header, but the product grid is still empty. Time to build the component that turns each product object into something visual.

In this step, you'll create a ProductCard component that receives product data as props and renders a styled card. You'll test it with a single product first, then add CSS to make it look polished.

In this step, get ready to:
  • Create a reusable ProductCard component that accepts product data through props.
  • Render a single card in your App to verify the component works.
  • Style the card with CSS for rounded corners, shadows, and a hover effect.
Create the ProductCard component file

In React, each piece of your UI lives in its own component file. Your ProductCard component will receive product data through its function parameters and return JSX that displays it.

  • In VS Code's Explorer sidebar, right-click the src folder.
  • Click New Folder and name it components.
  • Right-click the new components folder and click New File.
  • Name the file ProductCard.jsx.
  • Copy the following code into ProductCard.jsx:
import './ProductCard.css';

// Component receives product data as props
function ProductCard({ name, price, image, description }) {
  return (
    <div className="product-card">
      {/* Product image */}
      <img src={image} alt={name} className="product-image" />
      <div className="product-info">
        <h2 className="product-name">{name}</h2>
        <p className="product-description">{description}</p>
        <div className="product-footer">
          {/* Format price to 2 decimal places */}
          <span className="product-price">${price.toFixed(2)}</span>
        </div>
      </div>
    </div>
  );
}

export default ProductCard;

What does this code do?

  • function ProductCard({ name, price, image, description }) defines a component that receives four props. The curly braces in the parameter list pull each value out of the props object automatically.
  • The return block contains JSX, which looks like HTML but lives inside JavaScript. Each prop value gets injected into the JSX using curly braces like {name}.
  • price.toFixed(2) formats the price to always show two decimal places, so 59.9 becomes 59.90.
  • export default ProductCard makes this component available for other files to import.

What are props?

Props (short for "properties") are the inputs you pass to a React component. They work like function arguments.

When you write <ProductCard name="Shoes" />, the ProductCard component receives name with the value "Shoes". The component can then use that value anywhere in its JSX.

  • Save ProductCard.jsx.

You won't see a change in the browser yet. The next substep imports ProductCard into your App so it appears on the page.

Awesome, I've got everything!

Great. Double check you've saved ProductCard.jsx before moving on.

I'd like to double check the full code

import './ProductCard.css';

function ProductCard({ name, price, image, description }) {
  return (
    <div className="product-card">
      <img src={image} alt={name} className="product-image" />
      <div className="product-info">
        <h2 className="product-name">{name}</h2>
        <p className="product-description">{description}</p>
        <div className="product-footer">
          <span className="product-price">${price.toFixed(2)}</span>
        </div>
      </div>
    </div>
  );
}

export default ProductCard;
Render one ProductCard in App to test it

Before rendering all six products, it helps to test with a single card first. This way you can confirm the component works before adding complexity.

  • In App.jsx, find the line that says import products from './data/products';.
  • Add this import on a new line directly above it:
import ProductCard from './components/ProductCard';
  • Find the <main className="product-grid"> element inside your App function.
  • Add a single ProductCard inside it with hardcoded test values:
      <main className="product-grid">
        {/* Test with a single product card */}
        <ProductCard
          name="Wireless Headphones"
          price={59.99}
          image="https://picsum.photos/seed/headphones/300/200"
          description="Premium sound quality with noise cancellation."
        />
      </main>

What's happening here?

  • You're passing four props to ProductCard: name, price, image, and description. Each one maps to a parameter in the ProductCard function.
  • These are hardcoded test values from the first product in your data file. In the next step, you'll replace this with a loop that renders a card for every product automatically.
  • Notice that price={59.99} uses curly braces instead of quotes. Curly braces pass a JavaScript value (a number), while quotes pass a string.
  • Save App.jsx.
  • Check your browser. You should see a product image, the name "Wireless Headphones", its description, and the price "$59.99" displayed in the grid area.

The content looks unstyled right now. You'll fix that in the next substep.

Don't see the product card?

  • Make sure the import path matches exactly: './components/ProductCard'. The path is case-sensitive.
  • Check that ProductCard.jsx is inside the src/components folder, not directly inside src.
  • Look at your terminal for error messages. A red error usually points to a typo in the file path or the JSX syntax.
  • My ProductCard isn't showing up in the browser after saving App.jsx. What could be wrong?

Awesome, I've got everything!

Great. Double check you've saved App.jsx before moving on.

I'd like to double check the full code

import ProductCard from './components/ProductCard';
import products from './data/products';
import './App.css';

function App() {
  return (
    <div className="app">
      <header className="header">
        <h1>Product Showcase</h1>
        <p>Discover our latest collection</p>
      </header>
      <main className="product-grid">
        {/* Test with a single product card */}
        <ProductCard
          name="Wireless Headphones"
          price={59.99}
          image="https://picsum.photos/seed/headphones/300/200"
          description="Premium sound quality with noise cancellation."
        />
      </main>
    </div>
  );
}

export default App;
Style the ProductCard with CSS

Your ProductCard is rendering, but it needs visual polish. Let's add CSS to give it rounded corners, a shadow, and a smooth hover effect.

  • In VS Code's Explorer sidebar, right-click the components folder.
  • Click New File and name it ProductCard.css.
  • Copy the following styles into ProductCard.css:
/* Card container with rounded corners and shadow */
.product-card {
  background-color: #ffffff;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s, box-shadow 0.2s;
}

/* Lift effect on hover */
.product-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

/* Product image fills the card width */
.product-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

/* Text content area */
.product-info {
  padding: 16px;
}

.product-name {
  font-size: 1.1rem;
  margin: 0 0 8px 0;
  color: #1a1a2e;
}

.product-description {
  font-size: 0.9rem;
  color: #666;
  margin: 0 0 12px 0;
}

/* Footer holds price and future buttons */
.product-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.product-price {
  font-size: 1.2rem;
  font-weight: bold;
  color: #e94560;
}

What do these styles do?

  • .product-card gives the card a white background, rounded corners, and a subtle shadow. The overflow: hidden clips the image to the rounded corners.
  • The :hover rule lifts the card upward by 4 pixels and deepens the shadow when you move your mouse over it.
  • object-fit: cover ensures the product image fills its container without stretching or distorting.
  • .product-footer uses flexbox to space items apart horizontally. Right now it only holds the price, but you'll add a favorite button here in a later step.
  • Save ProductCard.css and check your browser.
  • You should see a white card with rounded corners, a product image at the top, the name "Wireless Headphones" below it, a description, and the price "$59.99" in red. Hover over the card to see it lift slightly.

Card still looks unstyled?

  • Make sure ProductCard.css is saved inside the src/components folder, right next to ProductCard.jsx.
  • Verify that ProductCard.jsx has import './ProductCard.css'; at the top. Without this import, Vite does not know to load the CSS.
  • Check that the class names in your CSS (like .product-card) match the className values in your JSX exactly. Class names are case-sensitive.
  • My ProductCard styles aren't applying. What should I check?

Awesome, I've got everything!

Great. Double check you've saved ProductCard.css before moving on.

I'd like to double check the full code

.product-card {
  background-color: #ffffff;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s, box-shadow 0.2s;
}

.product-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

.product-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.product-info {
  padding: 16px;
}

.product-name {
  font-size: 1.1rem;
  margin: 0 0 8px 0;
  color: #1a1a2e;
}

.product-description {
  font-size: 0.9rem;
  color: #666;
  margin: 0 0 12px 0;
}

.product-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.product-price {
  font-size: 1.2rem;
  font-weight: bold;
  color: #e94560;
}

Your ProductCard component is working and styled. Next up, you'll render the full grid of products by looping through your data array with JavaScript's .map() method.

Render the Full Product Grid

Your page shows a single product card with hardcoded data. Meanwhile, your products.js file has 6 products waiting to be displayed.

JavaScript's .map() method lets you loop over an array and render a React component for each item. This is one of the most common patterns in React development.

In this step, get ready to:
  • Replace the hardcoded ProductCard with a dynamic .map() loop.
  • Verify all 6 product cards display in a responsive grid.
Use .map() to render all products

The .map() method takes an array, runs a function on each item, and returns a new array of results. In React, you use it to turn an array of data into an array of components.

  • In App.jsx, find the hardcoded <ProductCard> inside the <main> tag.
  • Find this code:
<ProductCard
  name="Wireless Headphones"
  price={59.99}
  image="https://picsum.photos/seed/headphones/300/200"
  description="Premium sound quality with noise cancellation."
/>
  • Replace the entire hardcoded <ProductCard ... /> block with this .map() call:
{products.map(function(product) {
  return (
    <ProductCard
      key={product.id}
      name={product.name}
      price={product.price}
      image={product.image}
      description={product.description}
    />
  );
})}

What does this code do?

  • products.map() loops over each object in the products array and returns a <ProductCard> for each one.
  • function(product) gives you access to each product object inside the loop.
  • key={product.id} is a special React prop that helps React track which items change when the list updates.
  • Each prop like name={product.name} pulls the matching value from the current product object.
  • Save App.jsx.
  • Open your browser and check http://localhost:5173. You should see 6 product cards arranged in a grid layout.

Only seeing one card or none?

  • Make sure you removed the entire hardcoded <ProductCard ... /> block and replaced it with the {products.map(...)} code.
  • Check that your products.js file exports the array with export default products at the bottom.
  • Open your browser console (F12) and look for any red error messages about missing keys or imports.
  • Help me debug my product grid.
Verify the grid and adjust styling

The CSS Grid layout you added in Step 2 handles the responsive grid automatically. No extra styling is needed for the grid to work with 6 cards.

  • Try resizing your browser window narrower.
  • Watch the grid adapt from 3 columns to 2 columns, then to a single column on very narrow screens.

How does the grid adapt automatically?

The grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)) rule in your .product-grid CSS tells the browser to create as many columns as can fit, with each column at least 280px wide. As the window shrinks, fewer columns fit, so the layout reorganizes itself.

Awesome, I've got everything!

Double check you've saved App.jsx and all 6 cards are showing in your browser.

I'd like to double check the full code

Here is the complete App.jsx file after this step.

import ProductCard from './components/ProductCard';
import products from './data/products';
import './App.css';

function App() {
  return (
    <div className="app">
      <header className="header">
        <h1>Product Showcase</h1>
        <p>Discover our latest collection</p>
      </header>
      <main className="product-grid">
        {products.map(function(product) {
          return (
            <ProductCard
              key={product.id}
              name={product.name}
              price={product.price}
              image={product.image}
              description={product.description}
            />
          );
        })}
      </main>
    </div>
  );
}

export default App;

Your product grid is complete with all 6 cards rendering dynamically from data. Next up, you'll bring the cards to life by adding a favorite button that responds to clicks.

Add Interactivity with State

Your product grid looks great, but the cards are completely static. Clicking on them does nothing. Real web apps respond to user actions.

In this step, you will add a favorite button to each product card. Clicking the heart toggles it between empty and filled. Each card remembers its own favorited status using React state.

In this step, get ready to:
  • Add a state variable to track whether a product is favorited.
  • Create a click handler that toggles the favorite state.
  • Style the favorite button with hover effects and visual feedback.
Add a favorite button with useState

React components need a way to remember things between renders. A button click count, a form input value, or whether something is toggled on or off. This is what state is for.

What is useState?

The useState hook is a function from React that gives your component a piece of memory. You call it with an initial value, and it returns two things: the current value and a function to update it.

When you call the updater function, React re-renders the component with the new value. The UI stays in sync with the data automatically.

  • In VS Code, open src/components/ProductCard.jsx.
  • Find the import line at the top of the file:
import './ProductCard.css';
  • Replace it with these two import lines:
import { useState } from 'react';
import './ProductCard.css';

What does this import do?

This imports the useState function from the react package. The curly braces mean you are importing a specific named export, not the default export.

  • Now add the state variable inside your ProductCard function, on the very first line before the return statement:
const [isFavorite, setIsFavorite] = useState(false);

What does this line do?

  • useState(false) creates a state variable that starts as false (not favorited).
  • isFavorite holds the current value.
  • setIsFavorite is the function you call to change the value and trigger a re-render.
Handle the click event and toggle the state

You have a state variable, but nothing changes it yet. You need a function that flips isFavorite between true and false when the user clicks the heart button.

  • In ProductCard.jsx, replace the entire component with this updated version that includes the handler function and favorite button:
import { useState } from 'react';
import './ProductCard.css';

function ProductCard({ name, price, image, description }) {
  const [isFavorite, setIsFavorite] = useState(false);

  function handleFavoriteClick() {
    setIsFavorite(!isFavorite);
  }

  return (
    <div className="product-card">
      <img src={image} alt={name} className="product-image" />
      <div className="product-info">
        <h2 className="product-name">{name}</h2>
        <p className="product-description">{description}</p>
        <div className="product-footer">
          <span className="product-price">${price.toFixed(2)}</span>
          <button
            className={`favorite-button ${isFavorite ? 'favorited' : ''}`}
            onClick={handleFavoriteClick}
          >
            {isFavorite ? '\u2764\uFE0F' : '\uD83E\uDD0D'}
          </button>
        </div>
      </div>
    </div>
  );
}

export default ProductCard;

What does this code do?

  • handleFavoriteClick() flips isFavorite to its opposite value. If it was false, it becomes true, and vice versa.
  • onClick={handleFavoriteClick} tells React to call that function whenever the button is clicked.
  • The ternary expression isFavorite ? '\u2764\uFE0F' : '\uD83E\uDD0D' is conditional rendering. It shows a filled red heart when favorited and a white heart when not.
  • The className uses a template literal to add the favorited class only when the product is favorited.
  • Save ProductCard.jsx.
  • Refresh your browser. You should see a white heart emoji appear to the right of each product's price.
  • Click a heart. It should toggle to a red filled heart. Click it again and it returns to white.

Heart not toggling?

  • Make sure the onClick prop uses a capital C. React event names are camelCase, not lowercase like HTML.
  • Check that handleFavoriteClick is defined inside the ProductCard function, not outside it. It needs access to setIsFavorite.
  • Verify you wrote onClick={handleFavoriteClick} and not onClick={handleFavoriteClick()}. Adding parentheses calls the function immediately instead of passing it as a handler.

Why isn't my favorite button working in React?

Why does each card remember its own state?

Each ProductCard is a separate instance of the component. When React renders six cards, each one gets its own independent copy of isFavorite. Clicking the heart on one card does not affect the others.

Awesome, I've got everything!

Double check you have saved your file before moving on.

I'd like to double check the full code

Here is the complete ProductCard.jsx file at this point.

import { useState } from 'react';
import './ProductCard.css';

function ProductCard({ name, price, image, description }) {
  const [isFavorite, setIsFavorite] = useState(false);

  function handleFavoriteClick() {
    setIsFavorite(!isFavorite);
  }

  return (
    <div className="product-card">
      <img src={image} alt={name} className="product-image" />
      <div className="product-info">
        <h2 className="product-name">{name}</h2>
        <p className="product-description">{description}</p>
        <div className="product-footer">
          <span className="product-price">${price.toFixed(2)}</span>
          <button
            className={`favorite-button ${isFavorite ? 'favorited' : ''}`}
            onClick={handleFavoriteClick}
          >
            {isFavorite ? '\u2764\uFE0F' : '\uD83E\uDD0D'}
          </button>
        </div>
      </div>
    </div>
  );
}

export default ProductCard;
Add visual feedback with CSS

The heart toggles, but the button still looks like a default browser button. You need styles that remove the default appearance and add a satisfying hover effect.

  • Open src/components/ProductCard.css.
  • Add these styles at the bottom of the file:
.favorite-button {
  background: none;
  border: none;
  font-size: 1.4rem;
  cursor: pointer;
  transition: transform 0.2s;
  padding: 4px;
}

.favorite-button:hover {
  transform: scale(1.2);
}

What do these styles do?

  • background: none and border: none strip away the default browser button appearance.
  • cursor: pointer shows a hand icon on hover so users know it is clickable.
  • transform: scale(1.2) on hover makes the heart grow slightly, giving satisfying visual feedback.
  • Save ProductCard.css and refresh your browser.
  • Hover over a heart. It should scale up smoothly. The button no longer has a visible border or background.
  • Click hearts on different cards to confirm each card manages its own favorite state independently.

Button still looks like a default button?

  • Make sure the class name in your CSS (.favorite-button) matches the className in your JSX (favorite-button). CSS class names are case-sensitive.
  • Check that the CSS import import './ProductCard.css' is still at the top of your ProductCard.jsx file.

Why aren't my CSS styles applying to the favorite button?

Your product showcase is now fully interactive. Each card responds to clicks, remembers its favorite status, and gives the user smooth visual feedback. You have used every core React pattern: components, props, state, event handling, conditional rendering, and list rendering.

Secret mission

Add a Category Filter

Your product showcase displays every product at once. What if users want to browse just Electronics or Home items? Add a category filter dropdown that lets users narrow down the product grid instantly.

Before you go

Clean Up Your Resources

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

Resources you used:

  • Vite development server (running on localhost:5173).
  • The product-showcase project folder and all its files.
  • Node.js and npm (pre-existing on your machine).
  • VS Code (pre-existing on your machine).

Keep everything running

No action needed. Choose this if you are still actively building or want to continue experimenting with your product showcase page.

  • Your Vite dev server stays running in the terminal.
  • You can keep editing files in VS Code and see live updates in the browser.

Pause - I'll come back to this later

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

  • Go to the terminal where the Vite dev server is running.
  • Press Ctrl + C to stop the server.
  • Your project files stay on your machine. When you are ready to pick up again, open the project folder in VS Code and run:
cd product-showcase
npm run dev

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Go to the terminal where the Vite dev server is running and press Ctrl + C to stop it.
  • Navigate to the parent directory that contains your project folder.
  • Delete the entire project folder by running:

macOS

rm -rf product-showcase

Windows

rmdir /s /q product-showcase

Linux

rm -rf product-showcase

Node.js and npm are general-purpose tools you will likely need for other projects. There is no need to uninstall them unless you are sure you will not use them again.

Mission accomplished

Nice Work

You just built a fully interactive product showcase page with React from scratch. That is a real, working React application running in your browser.

Here's what you accomplished:

  • Built reusable components that accept data through props and rendered a dynamic product grid using JavaScript's .map() method.
  • Added interactivity with useState and event handlers, giving each product card its own independent favorite toggle.
  • Styled a responsive layout with CSS Grid and custom component styles that adapt to different screen sizes.
  • Added a category filter dropdown using .filter() and a controlled <select> element to dynamically show products by category.

Ready to quiz yourself?