Search and filter functionality is straightforward, yet I often need to refer back to documentation. The biggest thing to remember is the information flow between the components - where does state live, where it's passed, and sending data from a child component to its parent via a callback function. At first, this functionality can feel like a confusing loop, but after you construct components like these a few times, the basic steps get easier to remember.
Recently I built a React app from scratch as a coding Bootcamp project. The app is called Scientific Foodie, a database that shares scientific information about all different types of food, such as fruits, vegetables, grains, gourds, and more:
Because I was building a database, I decided to have search and filter functionality so users could interact with the database to the fullest.
In this app, my Search and Filter components work in tandem, so below, I walk through the steps of constructing each component and how I, and here's a hint, chained them together.
And since my Search and Filter components are siblings and children of my FoodContainer component, I determined that FoodContainer would hold state and handle the callback functions. Why? Because sibling components can't pass data to each other directly. Data can only flow up and down between parent and child. Therefore their parent component, FoodContainer, will hold state and handle the overall functionality.
Making your Filter Component 📌
Once you have the basic JSX written, i.e., what you're filtering by, it's time to construct the functionality. The value of your filter options should match the data key you're filtering. My app filters my food by group.
In your parent component, create state that will capture the user's selected filter. I like to set default state for my filter to "All," which is the first option in my Filter component. "All" ensures everything displays upon page load, and while filtering, the user can return to seeing everything at any point.
-
Next, we'll create a simple callback function to handle capturing the event.target.value, which is the filter change, and updating state. Initially, I always set my helper function to console log the selection, just so I know my callback is working — more on this in a minute.
Pass the helper function as props from your parent component to your child, the Filter component. This allows the callback to be owned by a different component than the one invoking it.
-
Filter functionality takes an onChange event. Simply assign the helper function as props to onChange. Each time a filter option is selected, the value is passed up through our helper function to our parent component.
-
Try this out to make sure it works! How will you know? Remember, we have a temporary console log in the helper function in our parent component. In your app, select a filter option and see the value logged in your console. Again, make sure your option values match your data.
Now that you know the callback works remove the console log and replace it with your state's setter function. And add one more temporary console log to check the value of your state variable. In your app, select a filter again, and you'll see your state capturing the change in filters in the console! Finally, remove the console log.
Making your Search Component 🔎
Leaving the Filter component aside, for now, let's construct our Search.
Set up state in the top-level parent component, in my case FoodContainer. The default value for your search state should be an empty string.
-
Create a simple callback function to handle what's typed in the search bar. As we initially did with our filter helper function, set your search helper function to console log the text in the search bar. We want to make sure it works.
Next, your Search component should take two props, the search state variable, and the helper function. We're passing state down as props because we're constructing a controlled component.
-
In your Search component, the value should take the search state variable as props, and your onChange takes the helper callback as props. Your helper is capturing the event.target.value.
Try this out to make sure it works! With the temporary console log in our search helper function in our parent component, type something into your search bar. In the console, you'll see each keystroke logged as a single letter at a time. That's ok! We're not updating state just yet; we're only console logging to ensure the callback is working correctly.
-
Next, remove that console log and replace it with the search state's setter function. Then add one more temporary console log to check that your state is updating correctly and type again in the search bar. You'll see the text in the console and the word forming in the search bar. Cool! Because this is a controlled component with state assigned as the search value, the letters appear in the search bar as you type, aka the user is watching state update in real-time.
Finally, remove that last console log.
Chaining Filter and Search together ⛓
The last step is updating the items displayed on the page based on what's being searched and filtered. In my case, I want my users to search for a specific food in the database based on the food's common name, filter by food group, and do these actions together.
My parent component FoodContainer handles displaying my food on the page, so that's where I added the following code:
Declare the variable that holds the array you'll map over. I originally mapped my entire foods array, but that array didn't have my search and filter logic built in. So, I created a variable that handled my search and filter; this array will be mapped to display my items instead.
Write an if statement. If the selected filter is "All," then all of the elements in the array will return. This is written as
return true
. Otherwise, return the elements that match the user's selected filter.
We could stop here; however, we want search and filter to work together so the user can utilize both. A couple more steps:
Next, chain on another filter. Now our variable,
foodsToDisplay
, takes both the filter and the search code into consideration instead of one or the other. Chaining methods is essentially calling methods one after another in one statement, allowing your code to be concise and the logic to continue.-
In my case, I'm allowing users to search for foods by common name.
includes
is added because I want to return partial matches as the user types. Finally, I'm converting the common name and the search to lowercase so capitalization doesn't get in the way.
TLDR 😎 - either the user searches through all the foods or searches a filtered selection.
Now, finally, let's map over the new array! Once done, try out search and filtering in your app. It's pretty cool.
So how did it go for me? Here's how my React project turned out:
Top comments (6)
Nice write, Lauren! I love the images you have. Can I get the reference where I can grab them?
Hi, and thank you! Do you mean the images of my code?
Ah, sorry. I mean the fruits and veges..
Gotcha. I used a mock API generator, Retool Utilities.
Also the code should definitely be copy-able, that would be very useful
It would be nice to have a reference where I could just see all the files at once and all the code at once