Filter List by category in React JS

In this article, we will build a React JS app to filter the list of items based on the category. The functionalities to be implemented are as follows:

  1. Display a list of all items on the screen.
  2. Allow user to select the category from the HTML dropdown.
  3. Display filtered list with items only from the selected category.
filtercategory
Filtering list of items by category

1. React JS state for the list of items and filter by a category

Based on our observation, both lists of items and selected categories are dynamic and frequently updated, hence we need to create following the React states to address that:

  1. sportList: React State to maintain the list of all sports from every category.
  2. selectedCategory: React State to keep track of the category selected by the user in <select> dropdown.
const [sportList, setSportList] = useState([]);

const [selectedCategory, setSelectedCategory] = useState();

2. React Component to display individual item

To achieve better code reusability and modularity, we will create a React Component called Item, specifically to display an individual item.

import React from "react";
import "./itemStyle.css";

const Item = ({ name, category }) => (
  <div className="item-container">
    <div>
      <span className="item-label">Name:</span>
      {name}
    </div>
    <div>
      <span className="item-label">Category:</span>
      {category}
    </div>
  </div>
);

export default Item;

3. Initializing default value for the list of items

The structure of JavaScript object consists of an array of objects with the following properties:

  1. name: The text associated with the name of sport displayed on the screen.
  2. category: The text associated with the category of sport used for filtering items and displayed on the screen.
var defaultSports = [
    { name: "Table Tennis", category: "Indoor" },
    { name: "Football", category: "Outdoor" },
    { name: "Swimming", category: "Aquatics" },
    { name: "Chess", category: "Indoor" },
    { name: "BaseBall", category: "Outdoor" }
];

In this example, we will assign the default value to sportList State since we are building application for demonstration purpose. React useEffect with “[]” dependency ensures that the callback function is executed when component is initially mounted.

useEffect(() => {
    setSportList(defaultSports);
}, []);

4. Creating dropdown to select the category

For the functionality of filter by category, we need to make sure that selectedCateogory state is up to date with the value selected from the dropdown. The handleCategoryChange function updates the selectedCateogory with the value passed by HTML dropdown element.

function handleCategoryChange(event) {
   setSelectedCategory(event.target.value);
}

Adding handleCategoryChange function as onChange handler for <select> element, ensures that the state is updated every time the option is changed.

<div className="filter-container">
   <div>Filter by Category:</div>
   <div>
      <select
        name="category-list"
        id="category-list"
        onChange={handleCategoryChange}
      >
         <option value="">All</option>
         <option value="Outdoor">Outdoor</option>
         <option value="Indoor">Indoor</option>
         <option value="Aquatics">Aquatics</option>
      </select>
   </div>
</div>

5. Filter list of items by category

Next, we will create a JavaScript function getFilteredList which returns the filtered value based on selected category derived from the value of sportList state. Alternatively, if no category is selected then an unfiltered array is returned.

function getFilteredList() {
  if (!selectedCategory) {
    return sportList;
  }
  return sportList.filter((item) => item.category === selectedCategory);
}

Using the function call directly will result in multiple repetitive function calls, hence useMemo ensures that the filteredList variable is recalculated only when either value of selectedCategory or sportList changes.

var filteredList = useMemo(getFilteredList, [selectedCategory, sportList]);

6. Render all elements of the list

Once the list of sports filtered by category is calculated, we apply array.map to render Item component for each entry with its properties passed down as props to the Item component.

<div className="sport-list">
  {filteredList.map((element, index) => (
     <Item {...element} key={index} />
  ))}
</div>

7. Including CSS stylings

Given that the JSX functionalities of React app are implemented, we need to add CSS style to improve the usability of the application. Please refer below for the CSS code of App.js and Item.js.

Final Solution Code

styles.css

.app {
  font-family: sans-serif;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: 20px;
  justify-content: center;
  align-items: center;
}
.filter-container {
  display: flex;
  align-items: center;
  gap: 10px;
}
#category-list {
  padding: 5px;
}
.sport-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}

App.js

import React, { useEffect, useMemo, useState } from "react";
import Item from "./components/Item";
import "./styles.css";

//Filter list by category in React JS
export default function App() {
  // Default Value
  var defaultSports = [
    { name: "Table Tennis", category: "Indoor" },
    { name: "Football", category: "Outdoor" },
    { name: "Swimming", category: "Aquatics" },
    { name: "Chess", category: "Indoor" },
    { name: "BaseBall", category: "Outdoor" }
  ];
  const [sportList, setSportList] = useState([]);

  const [selectedCategory, setSelectedCategory] = useState();

  // Add default value on page load
  useEffect(() => {
    setSportList(defaultSports);
  }, []);

  // Function to get filtered list
  function getFilteredList() {
    // Avoid filter when selectedCategory is null
    if (!selectedCategory) {
      return sportList;
    }
    return sportList.filter((item) => item.category === selectedCategory);
  }

  // Avoid duplicate function calls with useMemo
  var filteredList = useMemo(getFilteredList, [selectedCategory, sportList]);

  function handleCategoryChange(event) {
    setSelectedCategory(event.target.value);
  }

  return (
    <div className="app">
      <div className="filter-container">
        <div>Filter by Category:</div>
        <div>
          <select
            name="category-list"
            id="category-list"
            onChange={handleCategoryChange}
          >
            <option value="">All</option>
            <option value="Outdoor">Outdoor</option>
            <option value="Indoor">Indoor</option>
            <option value="Aquatics">Aquatics</option>
          </select>
        </div>
      </div>
      <div className="sport-list">
        {filteredList.map((element, index) => (
          <Item {...element} key={index} />
        ))}
      </div>
    </div>
  );
}

./components/Item.js

import React from "react";
import "./itemStyles.css";

// React Component to display individual item
const Item = ({ name, category }) => (
  <div className="item-container">
    <div>
      <span className="item-label">Name:</span>
      {name}
    </div>
    <div>
      <span className="item-label">Category:</span>
      {category}
    </div>
  </div>
);

export default Item;

./components/itemStyles.css

.item-container {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid black;
}
.item-label {
  font-weight: 600;
  margin-right: 8px;
}