Filtering Array & Array of objects in React JS

Filtering Array in React JS

First, let’s understand how to filter simple arrays in React JS. Filtering array can be done in 3 steps:

  1. Initialize the React state with the default array. 
  2. Define a handler function that calls the setState() method with filtered values as the parameter.
  3. Render the elements of the array with .map() which automatically updates the screen every time the filtered values are updated.
import React, { useState } from "react";

const employees = [
  "John",
  "Jane",
  "Tom",
  "Jermy",
  "Justin",
  "Blake",
  "Christian",
  "Martin"
];

export default function App() {
  const [employeeList, setEmployeeList] = new useState(employees);

  const filterByName = (event) => {
    // Name search query
    const query = event.target.value;

    // Get all employees
    var updatedList = [...employees];

    // Filter employee names by search query
    updatedList = updatedList.filter((item) => {
      return item.toLowerCase().indexOf(query.toLowerCase()) !== -1;
    });

    // Trigger render with updated values
    setEmployeeList(updatedList);
  };

  return (
    <div className="App">
      <div className="search-header">
        <div className="search-text">Search Employee:</div>
        <input id="search-box" onChange={filterByName} />
      </div>
      <div id="item-list">
        <ol>
          {employeeList.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ol>
      </div>
    </div>
  );
}

Filtering Array of objects in React JS

Now we will learn how to filter the array of objects in React JS. The React JS app will have the following features:

  1. Display a list of all cars with details and images.
  2. Filter car list by years ie displays all cars with release_year 2020.
  3. Filter car list by brand name from the dropdown, ie display all BMW cars.

1. Declare a constant for an array of objects

Every car is represented as a JS object with name, url, and release_year properties. The list of car objects is declared as an array assigned to “carList” JS constant.

const carList = [
    {
      name: "BMW M6",
      url:
        "https://mediapool.bmwgroup.com/cache/P9/201411/P90169551/P90169551-the-new-bmw-m6-coup-exterior-12-2014-600px.jpg",
      release_year: 2020
    },
    {
      name: "VW Polo",
      url:
        "https://cdn.euroncap.com/media/30740/volkswagen-polo-359-235.jpg?mode=crop&width=359&height=235",
      release_year: 2018
    },
    {
      name: "Audi S6",
      url:
        "https://www.motortrend.com/uploads/sites/5/2020/03/6-2020-audi-s6.jpg?fit=around%7C875:492.1875",
      release_year: 2020
    },
    {
      name: "BMW M2",
      url:
        "https://imgd.aeplcdn.com/0x0/cw/ec/37092/BMW-M2-Exterior-141054.jpg?wm=0",
      release_year: 2019
    },
    {
      name: "Audi A3",
      url: "https://cdn.motor1.com/images/mgl/BEooZ/s3/2021-audi-s3.jpg",
      release_year: 2019
    }
  ];

2. Declaring React state for filter values

We will declare the following React state to implement functionality in the component:

  1. filteredList: State for list of all car objects that satisfy all the filter conditions.
  2. selectedBrand: State for the brand_name value based on which filteredList state has to be filtered.
  3. selectedYear: State for the year which is used to filter car object based on release_year property.
  // List of all cars satisfing all the filters
  const [filteredList, setFilteredList] = useState(carList);

  // Selected Brand name filter
  const [selectedBrand, setSelectedBrand] = useState("");
  // Selected Year filter
  const [selectedYear, setSelectedYear] = useState();

3. Display the list of items on the screen

Now the declared array of car objects should be displayed on the screen through the React component. We apply Array.map() on filteredList React state to add details of every entry to JSX code.

The filteredList React state initially contains the list of all the elements, hence all the car details displayed on the screen. React JS automatically updates the list of car details displayed on the screen based on filteredList state value when additional filters are applied.

return (
  <div id="car-list">
    {filteredList.map((item, index) => (
       <div className="car-item" key={index}>
         <div className="car-name">{`Name: ${item.name}`}</div>
         <div className="car-year">{`Year: ${item.release_year}`}</div>
         <img className="car-image" src={item.url} alt="car-img" />
       </div>
    ))}
  </div>
);

4. Filter items by brand name using String methods

We declare a JS function that filters an array of car objects based on brand name with the following steps:

  1. Avoid filtering array when selectedBrand = “” .
  2. Filter filteredData array to include only car names with selectedBrand word.
  3. return filteredData array.
const filterByBrand = (filteredData) => {
  // Avoid filter for empty string
  if (!selectedBrand) {
    return filteredData;
  }

  const filteredCars = filteredData.filter(
    (car) => car.name.split(" ").indexOf(selectedBrand) !== -1
  );
  return filteredCars;
};

5. Filter items by Year using the Comparison operator

We declare JS function that filters array of car objects by release_year property with the following steps:

  1. Avoid filtering array when selectedYear == null
  2. Filter array to include all the car objects where release_year property matches selectedYear.
  3. return filteredData array.
const filterByYear = (filteredData) => {
    // Avoid filter for null value
    if (!selectedYear) {
      return filteredData;
    }

    const filteredCars = filteredData.filter(
      (car) => car.release_year === selectedYear
    );
    return filteredCars;
  };

6. Functions to handle filter change

We will create the following functions to handle changes in filter values:

  1. handleBrandChange: update selectedBrand state based on event target value.
  2. handleYearChange: Reset seletedYear if the same year is selected, else selectYear is updated with inputYear.
// Update seletedBrand state
const handleBrandChange = (event) => {
  setSelectedBrand(event.target.value);
};

// Toggle seletedYear state
const handleYearChange = (event) => {
  const inputYear = Number(event.target.id);

  if (inputYear === selectedYear) {
    setSelectedYear("");
  } 
  else {
    setSelectedYear(inputYear);
  }
};

7. Add HTML code for filters

To allow users to trigger filter functions, we need to add HTML code for both filters by brand name and year. We will create an HTML dropdown for selecting the brand name filter values. The options for the filter by year is displayed using <div></div> containers.

<div className="brand-filter">
  <div>Filter by Brand :</div>
  <select
    id="brand-input"
    value={selectedBrand}
    onChange={handleBrandChange}
  >
    <option value="">All</option>
    <option value="BMW">BMW</option>
    <option value="VW">VW</option>
    <option value="Audi">Audi</option>
  </select>
</div>
<div>Filter by Year</div>
<div id="year-options" onClick={handleYearChange}>
  <div
    className={selectedYear === 2018 ? "active-option" : "filter-option"}
    id="2018"
  >
    2018
  </div>
  <div
    className={selectedYear === 2019 ? "active-option" : "filter-option"}
    id="2019"
  >
    2019
  </div>
  <div
    className={selectedYear === 2020 ? "active-option" : "filter-option"}
    id="2020"
  >
    2020
  </div>
</div>

8. React Hooks to trigger an update for filtered values

The handle filter functions only update the selectedBrand and selectedYear states, but the filterByBrand and filterByYear JS functions are not triggered.

We will create a React useEffect() with a callback function that applies both filterByBrand and filterByYear on carList, then update filteredList state with the result. Both selectBrand and selectedYear states are added as the dependencies for useEffect() to ensure filter functionality is triggered every time any one of the values is changed.

useEffect(() => {
  var filteredData = filterByBrand(carList);
  filteredData = filterByYear(filteredData);
  setFilteredList(filteredData);
}, 
[selectedBrand, selectedYear]);
React JS array of object filters
The output of React JS Application

Final Solution Code

App.js

import { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  // Array of all car objects
  const carList = [
    {
      name: "BMW M6",
      url:
        "https://mediapool.bmwgroup.com/cache/P9/201411/P90169551/P90169551-the-new-bmw-m6-coup-exterior-12-2014-600px.jpg",
      release_year: 2020
    },
    {
      name: "VW Polo",
      url:
        "https://cdn.euroncap.com/media/30740/volkswagen-polo-359-235.jpg?mode=crop&width=359&height=235",
      release_year: 2018
    },
    {
      name: "Audi S6",
      url:
        "https://www.motortrend.com/uploads/sites/5/2020/03/6-2020-audi-s6.jpg?fit=around%7C875:492.1875",
      release_year: 2020
    },
    {
      name: "BMW M2",
      url:
        "https://imgd.aeplcdn.com/0x0/cw/ec/37092/BMW-M2-Exterior-141054.jpg?wm=0",
      release_year: 2019
    },
    {
      name: "Audi A3",
      url: "https://cdn.motor1.com/images/mgl/BEooZ/s3/2021-audi-s3.jpg",
      release_year: 2019
    }
  ];
  // List of all cars satisfing all the filters
  const [filteredList, setFilteredList] = useState(carList);
  // Selected Brand name filter
  const [selectedBrand, setSelectedBrand] = useState("");
  // Selected Year filter
  const [selectedYear, setSelectedYear] = useState();

  const filterByBrand = (filteredData) => {
    // Avoid filter for empty string
    if (!selectedBrand) {
      return filteredData;
    }

    const filteredCars = filteredData.filter(
      (car) => car.name.split(" ").indexOf(selectedBrand) !== -1
    );
    return filteredCars;
  };
  const filterByYear = (filteredData) => {
    // Avoid filter for null value
    if (!selectedYear) {
      return filteredData;
    }

    const filteredCars = filteredData.filter(
      (car) => car.release_year === selectedYear
    );
    return filteredCars;
  };

  // Update seletedBrand state
  const handleBrandChange = (event) => {
    setSelectedBrand(event.target.value);
  };

  // Toggle seletedYear state
  const handleYearChange = (event) => {
    const inputYear = Number(event.target.id);

    if (inputYear === selectedYear) {
      setSelectedYear("");
    } else {
      setSelectedYear(inputYear);
    }
  };

  useEffect(() => {
    var filteredData = filterByBrand(carList);
    filteredData = filterByYear(filteredData);
    setFilteredList(filteredData);
  }, [selectedBrand, selectedYear]);

  return (
    <div className="App">
      <div className="brand-filter">
        <div>Filter by Brand :</div>
        <select
          id="brand-input"
          value={selectedBrand}
          onChange={handleBrandChange}
        >
          <option value="">All</option>
          <option value="BMW">BMW</option>
          <option value="VW">VW</option>
          <option value="Audi">Audi</option>
        </select>
      </div>
      <div>Filter by Year</div>
      <div id="year-options" onClick={handleYearChange}>
        <div
          className={selectedYear === 2018 ? "active-option" : "filter-option"}
          id="2018"
        >
          2018
        </div>
        <div
          className={selectedYear === 2019 ? "active-option" : "filter-option"}
          id="2019"
        >
          2019
        </div>
        <div
          className={selectedYear === 2020 ? "active-option" : "filter-option"}
          id="2020"
        >
          2020
        </div>
      </div>

      <div id="car-list">
        {filteredList.map((item, index) => (
          <div className="car-item" key={index}>
            <div className="car-name">{`Name: ${item.name}`}</div>
            <div className="car-year">{`Year: ${item.release_year}`}</div>
            <img className="car-image" src={item.url} alt="car-img" />
          </div>
        ))}
      </div>
    </div>
  );
}

styles.css

.App {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100vh;
  font-size: 20px;
}
#year-options {
  display: flex;
  gap: 10px;
  margin: 10px;
}
.car-image {
  width: 200px;
  height: 180px;
}
#car-list {
  display: grid;
  gap: 20px;
  min-height: 480px;
  grid-template-columns: repeat(3, 1fr);
}
.car-item {
  display: flex;
  height: 200px;
  flex-direction: column;
  background-color: #fafafa;
  border: 1px solid black;
  padding: 10px;
  gap: 5px;
}
.filter-option,
.active-option {
  padding: 10px 20px;
  min-width: 50px;
  border-radius: 2rem;
}
.brand-filter {
  display: flex;
  margin: 15px;
}
#brand-input {
  width: 80px;
  margin: 0 10px;
  height: 30px;
}
.filter-option:hover,
.active-option {
  background-color: #48b393;
  color: white;
  cursor: pointer;
  display: flex;
  justify-content: center;
  transition: all 0.1s;
}