Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 74

Building a Product Search Feature

Recommended resources

Hello! Welcome back to the course.

In our last lesson, you did an excellent job rendering a dynamic grid of products using the .map() method and a reusable ProductCard component. Our product page now displays data from an API, but it's still a static list.

Today, we're going to make it interactive. Our goal is to implement a search feature to filter the displayed products based on user input. This lesson is a direct step towards your goal of building an e-commerce application and will give you hands-on practice with the Array.prototype.filter() method, a cornerstone of modern JavaScript that you've expressed a desire to master.

The Strategy: State, Input, and Filtering

To build a search feature on the client side, we need three key ingredients:

  1. A place to store the user's search term: We'll use the useState hook to create a piece of state, let's call it query, that holds the text from the search input.
  2. A way to capture user input: We'll add an <input> field to our page and use its onChange event to update the query state every time the user types.
  3. A way to filter the data: Before we .map() over our products array, we will first .filter() it, keeping only the products that match the current query.

This combination ensures that every time the user types a new character, the state updates, the product list is re-filtered, and the UI re-renders automatically to show the new results.


Capturing and Filtering: A Visual Guide

The process of connecting an input field to state and using that state to filter a list is a fundamental pattern in React. The following video provides a clear, step-by-step demonstration of this exact process.

React Search Filter Tutorial Beginner to Advanced

Watch this section from the "React Search Filter Tutorial" by Lama Dev. It demonstrates how to set up a state for a search query and use the .filter() method to dynamically update a list.

Watch from 02:24 to 06:38. Pay close attention to how useState is used to store the input's value and how .filter(), .toLowerCase(), and .includes() are combined to create a case-insensitive search.

As you saw, the core logic is surprisingly concise:
data.filter(item => item.firstName.toLowerCase().includes(query)).map(...)

This chain of methods is powerful. First, you filter the list down to only the relevant items, and then you map over that smaller, filtered list to create your components.

For a quick textual reinforcement of this pattern, the official React documentation has a great section on it.

Rendering Lists

The official React documentation offers a clear, concise explanation of using .filter() to render specific items from a list. This will reinforce the concept shown in the video.

Read the section titled "Filtering arrays of items". It provides a simple, direct example of using .filter() before .map() to control what gets rendered.


Your Turn: Implementing the Search Feature

Now, let's apply this to our e-commerce app.

1. Open src/pages/ProductsPage.jsx

We'll be adding the search logic to this file.

2. Add State for the Search Query

First, make sure you've imported useState from React. Then, inside your ProductsPage component function (before the return statement), add a new state variable to hold the search query.

import React, { useState, useEffect } from 'react';
// ... other imports

function ProductsPage() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [query, setQuery] = useState(''); // <-- ADD THIS LINE

  // ... useEffect hook for fetching data ...
  
  // ... rest of the component
}

3. Add the Search Input to Your JSX

In the return statement, let's add an <input> field above the product grid. We'll also add some basic styling to make it look clean.

// ... inside the ProductsPage component's return statement ...

return (
  <div style={{ padding: '1rem' }}>
    <h1>Our Products</h1>
    <input
      type="text"
      placeholder="Search products..."
      value={query} // Connect the input's value to our state
      onChange={e => setQuery(e.target.value)} // Update state on change
      style={{
        width: '100%',
        padding: '0.5rem',
        marginBottom: '1rem',
        fontSize: '1rem',
        boxSizing: 'border-box' // Ensures padding doesn't add to the width
      }}
    />

    {/* ... your existing loading, error, and grid rendering logic ... */}
  </div>
);

Now, if you run the app, you should see a search bar. You can type in it, but it won't do anything yet.

4. Filter the Products Before Mapping

This is the final and most important step. We will create a new variable, filteredProducts, which will hold the result of filtering our original products array based on the query.

Modify your component like this:

// ... inside the ProductsPage component, before the return statement ...

const filteredProducts = products.filter(product =>
  product.title.toLowerCase().includes(query.toLowerCase())
);

// In your return statement, update the .map() call
return (
  <div style={{ padding: '1rem' }}>
    {/* ... search input ... */}

    {loading && <p>Loading products...</p>}
    {error && <p>Error: {error}</p>}
    
    <div style={productGridStyles}>
      {filteredProducts.map(product => ( // <-- USE filteredProducts HERE
        <ProductCard
          key={product.id}
          // ... other props
        />
      ))}
    </div>
  </div>
);

That's it! Now, when you type in the search bar, the query state updates, the filteredProducts array is recalculated on every render, and React displays only the products whose titles match your search term.


Enhancement: Searching Multiple Fields

Right now, our search only looks at the product's title. A better user experience would be to search across multiple fields, like the title, description, and category.

You could do this with multiple || (OR) conditions, but there's a more elegant and scalable way using another array method you wanted to master: .some(). The .some() method checks if at least one element in an array passes a test.

The video you watched earlier demonstrates this exact technique.

React Search Filter Tutorial Beginner to Advanced

Let's revisit the Lama Dev video to see how to build a more robust search that works across multiple data fields. This is a powerful and clean pattern.

Watch from 09:44 to 15:32. The video first shows a less-efficient method using || operators, then introduces the superior approach using an array of keys and the .some() method. This is the part to focus on.

Let's adapt this pattern for our project. Our product objects from FakeStoreAPI have title, description, and category fields that we can search.

Update your filtering logic in ProductsPage.jsx to use this new pattern:

// ... inside the ProductsPage component, before the return statement ...

// An array of the keys we want to search in
const searchableKeys = ['title', 'description', 'category'];

const filteredProducts = products.filter(product => {
  // Return true if at least one of the keys' values includes the query
  return searchableKeys.some(key =>
    product[key].toLowerCase().includes(query.toLowerCase())
  );
});

// The rest of your component remains the same!

Notice the use of product[key]. Since key is a variable holding a string like "title", this is how you dynamically access a property of an object. This is a much cleaner solution than writing a long chain of || conditions and is easily extendable if you want to add more searchable fields later.


Conclusion

Fantastic work today! You have successfully implemented a client-side search feature, making our product catalog much more interactive.

Key Takeaways:

  • Controlled Inputs: You created a "controlled" input field by linking its value and onChange handler to React state.
  • Declarative Filtering: You used the Array.prototype.filter() method to declaratively create a new array of products based on a search query.
  • Method Chaining: You saw how .filter() and .map() can be chained together to create powerful and readable data transformations for rendering lists.
  • Scalable Search: You learned an advanced pattern using .some() to efficiently search across multiple properties of an object.

Preview of the Next Lesson:

Our product page is getting more powerful! The next logical step is to add another way for users to refine the product list. In the next lesson, we will implement a category filter, allowing users to select a category (e.g., "electronics", "jewelery") and see only the products within it. We'll also explore how to make both the search and category filters work together.