Hola readers!!! Vanakkam Makkale!!!
In this blog, we will explore how to create a filter component in React that’s both powerful and user-friendly. We will start by discussing the benefit of using a filter component and how it can enhance the user experience. Then, we will dive into the technical details of building a filter component in React, including the use of state and props, handling events, and rendering the results.
Whether you’re building an e-commerce platform, a search engine, or a data-heavy application, a filter component can help your users find exactly what they’re looking for in a flash, thus improving the user experience. So let’s roll up our sleeves, fire up our favorite code editor, grab a cup of coffee (or your beverage of choice) and get ready to build a filter component that will make your users’ lives easier and your codebase cleaner.
We will be creating a component for front-end filtering and we will NOT send the filters back to the server (through query) to get the filtered response.
Let’s do this!
Getting Started
We will be using create-react-app to set up the base project for this tutorial. Run the following command in the terminal:
npx create-react-app filterafter the setup,
- Delete all the files in
srcfolder exceptindex.jsandindex.css. - Remove all the contents in the
index.jsfile and paste the following code
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);3. Remove all the files in public folder except index.html
4. Create app.jsx under the src folder and use rafce (react es7 snippet) to create a template component.
5. Install Tailwind CSS by using
npm install -D tailwindcss
npx tailwindcss init -p5.1. Configure your template paths and add all of your template files in your tailwind.config.js file.
/** tailwind.config.js */
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{html,js,jsx,ts,tsx}",
"./src/**/**/*.{html,js,jsx,ts,tsx}",
"./src/pages/**/*.{html,js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}5.2. Add the @tailwind directives for each Tailwind’s layers to your index.css file. Remove all the CSS code in the file.
/** global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;6. Run npm start in the terminal to start the localhost:3000 where our React app will be hosted. We can also see all our changes there.
Data
DummyJSON is a free online REST API that you can use whenever you need some placeholder data for your front-end website or single-page application without running any server-side code. It’s awesome for teaching purposes, sample codes, testing, prototyping.
We are going to use the products endpoint of the dummyjson API, instead of using a dummy local data, to simulate real-world scenario. Here is an example of the response:
{
"products": [
{
"id": 1,
"title": "iPhone 9",
"description": "An apple mobile which is nothing like apple",
"price": 549,
"discountPercentage": 12.96,
"rating": 4.69,
"stock": 94,
"brand": "Apple",
"category": "smartphones",
"thumbnail": "...",
"images": ["...", "...", "..."]
},
{...},
{...},
{...}
// 30 items
],
"total": 100,
"skip": 0,
"limit": 30
}We will also be using /products/categories endpoint to get the list of categories and here is an example of the response:
[
"smartphones",
"laptops",
"fragrances",
"skincare",
"groceries",
"home-decoration",
"furniture",
"tops",
"womens-dresses",
"womens-shoes",
"mens-shirts",
"mens-shoes",
"mens-watches",
"womens-watches",
"womens-bags",
"womens-jewellery",
"sunglasses",
"automotive",
"motorcycle",
"lighting"
]And we will also implement this component without this products/categories API (by manually collecting all the categories from the products list) in later part of this blog.
Getting data from API
Lets create a async function getCategories to call the API to get all the categories and then we can store it in categories state variable.
// App.jsx
const [loading, setLoading] = useState(false);
const getCategories = async () => {
setLoading(true);
await fetch('https://dummyjson.com/products/categories')
.then(res => res.json())
.then(data =>
setCategories(data);
})
.catch(err => alert(err))
.finally(()=>{
setLoading(false);
})
}Also, we will create a logic to get all the categories from the product list, without using this API, in the later part of this blog.
Now, we can go ahead and get all the products from the API,
// App.jsx
const [productList, setProductList] = useState([]);
const getProducts = async () => {
setLoading(true);
await fetch('https://dummyjson.com/products')
.then(res => res.json())
.then(data => .then(data => {
setProductList(data.products);
getCategories(); // get the categories list
})
.catch(err => alert(err))
.finally(()=>{
setLoading(false);
})
}
useEffect(() => {
getProducts();
}, [])Here we have defined a async function getProducts to call the API and then we have stored the products-list in our productList state variable. Then, we used useEffect , which will trigger when the component mounts, to call the getProducts function.
Building the Filter Component & Logic
With all the data in place, we can proceed with developing the core logic for selecting categories and filtering the data.
Lets create a state selectedCategories to store the list of all the selected categories. Please take note that we will be designing this component to enable simultaneous selection of multiple categories.
const [selectedCategories, setSelectedCategories] = useState([]);And we can create functions to add, remove and reset this list of selected categories.
const addCategory = (category) => {
if(!selectedCategories.includes(category)){
setSelectedCategories(prev => ([...prev, category]))
}
}
const removeCategory = (category) => {
if(selectedCategories.includes(category)){
const removedList = selectedCategories.filter((item) => (item !== category));
setSelectedCategories(removedList);
}
}
const resetCategory = () => { // this function will be used to clear the filter
setSelectedCategories([]);
}Okaayyy!!! Lets ….

…develop the logic for filtering data.
const [fileredProductList, setFilteredProductList] = useState([]);
useEffect(() => {
if(selectedCategories.length === 0){
setFilteredProductList(productList);
} else{
setFilteredProductList(productList.filter((item)=>(selectedCategories.includes(item.category))));
}
}, [selectedCategories, productList])Here, we have created a state variable filteredProductList to store the filtered list of products and defined an useEffect that will be triggered whenever selectedCategories and productList variables changes.
In the event that the user modifies the filters, such as adding or removing a category, the following block of code will be executed. Within this block of code, we initially verify whether the selectedCategories list is empty. If it is empty, the filteredProductList will be set to the original productList. However, if it is not empty, we will extract the product items from the productList that have the selected category.
The Main Components
Using the categories and filteredProducts state variables, let’s create filter tags and product card.
<div className='relative w-full h-[15%] flex items-center overflow-x-auto'>
<span className='mx-3 ml-5 font-medium'> Categories: </span>
{
categories.map((category) => (
<div
onClick={() => {
if(selectedCategories.includes(category)){
removeCategory(category);
} else{
addCategory(category);
}
}}
className={`w-fit min-w-fit h-8 mx-2 px-5 py-2 flex flex-row justify-center items-center text-sm border break-keep rounded-3xl cursor-pointer transition-all duration-300 ${(selectedCategories.includes(category))?'border-blue-500 bg-blue-500 text-white':' border-gray-500 bg-white text-gray-900'} `}>
{category.split("-").join(" ")}
</div>
))
}
<div
onClick={() => resetCategory()}
className={`${(selectedCategories.length>0)?'opacity-100':'opacity-0 pointer-events-none'} sticky right-0 w-fit h-full px-5 flex justify-center items-center text-blue-500 bg-white backdrop-blur-lg cursor-pointer hover:text-blue-700 transition-all duration-300`}
>
clear
</div>
</div> In this section, we have implemented filter tags that allow for the addition and removal of categories depending on whether they have been selected or not. This is how it looks



Now, lets create simple ProductList component to display the product items. Create a new file named ProductList.jsx under src folder
We will filteredProducts state, that contains the filtered list of product items, and not the productList state variable.
// ProductList.jsx
const ProductList = ({filteredProductList, loading}) => {
if(loading)
return <>Loading</> // use your loading state or component
return (
<div className="w-full h-[85%] px-5">
<div className="w-full">Products: </div>
<div className="w-full h-full flex flex-wrap gap-1 justify-between items-start overflow-y-auto">
{
filteredProductList.map((product) => (
<div key={product.id} className='w-[19%] h-fit my-3 rounded-xl overflow-hidden border border-gray-200'>
<img
src={product.thumbnail}
alt='product'
className='w-full h-28 object-cover'
/>
<div className="mt-2 mb-2 px-3">
<div className="font-semibold">
{(product.title.length > 25)? product.title.substring(0,22) + '...': product.title}
</div>
<div className="text-sm text-gray-600">
{product.category}
</div>
</div>
</div>
))
}
</div>
</div>
)
}
export default ProductListHere is how it looks:

Getting the categories list from products API (without categories API) — To get list of all categories manually, instead of using the API, we can modify the getCategories function like this:
const getCategories = (products) => {
setLoading(true);
if(products.length > 0){
const unique_categories = [];
products.map((item) => {
if(!unique_categories.includes(item.category)){
unique_categories.push(item.category);
}
})
setCategories(unique_categories);
} else {
setCategories([]);
}
setLoading(false);
}Note: We have passed the list of product items to this function, so if you are using this function then change the function calling in getProducts from getCategories() to getCategories(data.products).
Wrapping up
Yay!!! We have completed the component.

Lets have a look at App.jsx.
import React, { useEffect, useState } from 'react'
import ProductList from './ProductList';
const App = () => {
const [loading, setLoading] = useState(false);
const [productList, setProductList] = useState([]);
const [categories, setCategories] = useState([]);
const [selectedCategories, setSelectedCategories] = useState([]);
const [fileredProductList, setFilteredProductList] = useState([]);
const addCategory = (category) => {
if(!selectedCategories.includes(category)){
setSelectedCategories(prev => ([...prev, category]))
}
}
const removeCategory = (category) => {
if(selectedCategories.includes(category)){
console.log(selectedCategories)
const removedList = selectedCategories.filter((item) => (item !== category));
setSelectedCategories(removedList);
}
}
const resetCategory = () => {
setSelectedCategories([]);
}
useEffect(() => {
if(selectedCategories.length === 0){
setFilteredProductList(productList);
} else{
setFilteredProductList(productList.filter((item)=>(selectedCategories.includes(item.category))));
}
}, [selectedCategories, productList])
const getCategories = async () => {
setLoading(true);
await fetch('https://dummyjson.com/products/categories')
.then(res => res.json())
.then(data => {
setCategories(data);
})
.catch(err => alert(err))
.finally(()=>{
setLoading(false);
})
}
const getProducts = async () => {
setLoading(true);
await fetch('https://dummyjson.com/products')
.then(res => res.json())
.then(data => {
setProductList(data.products);
setFilteredProductList(data.products);
getCategories(); // get the categories list
})
.catch(err => alert(err))
.finally(()=>{
setLoading(false);
})
}
useEffect(() => {
getProducts();
}, [])
return (
<div className='w-screen h-screen px-5 bg-gray-100 flex justify-center items-center'>
<div className='w-full h-[90%] rounded-md bg-white'>
<div className='relative w-full h-[15%] flex items-center overflow-x-auto'>
<span className='mx-3 ml-5 font-medium'> Categories: </span>
{
categories.map((category) => (
<div
onClick={() => {
if(selectedCategories.includes(category)){
removeCategory(category);
} else{
addCategory(category);
}
}}
className={`w-fit min-w-fit h-8 mx-2 px-5 py-2 flex flex-row justify-center items-center text-sm border break-keep rounded-3xl cursor-pointer transition-all duration-300 ${(selectedCategories.includes(category))?'border-blue-500 bg-blue-500 text-white':' border-gray-500 bg-white text-gray-900'} `}>
{category.split("-").join(" ")}
</div>
))
}
<div
onClick={() => resetCategory()}
className={`${(selectedCategories.length>0)?'opacity-100':'opacity-0 pointer-events-none'} sticky right-0 w-fit h-full px-5 flex justify-center items-center text-blue-500 bg-white backdrop-blur-lg cursor-pointer hover:text-blue-700 transition-all duration-300`}
>
clear
</div>
</div>
<ProductList filteredProductList={fileredProductList} loading={loading} />
</div>
</div>
)
}
export default AppHere is the final output:

Feel free to check the whole project on GitHub.
I hope you have enjoyed working on this project as much as I did. Please let me know your comments and suggestions.




