How do I filter a search result by category using dropdown options from API in React?

Viewed 60

I had to implement two main functionalities:

  • One is getting data from an API and then filtering it through a search bar which I've done - regarding this, can I pass a setState as a prop from one component to another? The search bar is working when I put it in the same component but doesn't even if I pass it as a callback function in the parent component.

  • Secondly and most importantly, I need to put two dropdown options (Country and Vehicle in this case) and filter the search data if it matches the option selected from the dropdown.

Here is the sandbox Link

Thanks.

1 Answers

Few things that need changing in your app.

You are managing your data state inside the Dropdown component, but this data state needs to be shared between the Dropdown, and the Search components, so is best to have it at the root of them, in this case App.

You can then pass the state to the App child components, Dropdown, and Search.

I only focused on the Dropdown component, and the implementation I will show you can be improved, but it will be easy to follow and it will give you a good starting point in my opinion, if not feel free to ask.

App.js

I have move the logic to fetch the data and the state here, and then we pass it to the Dropdown component as props.

You can do the same for the Search component, by creating a search term state for example.

There is an extra useEffect that will take care of updating the filteredData state based on the selectedCountry and selectedVehicle state values.

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

function App() {
  const [data, setData] = useState([]);
  const [filteredData, setFilteredData] = useState([]);
  const [selectedCountry, setSelectedCountry] = useState("");
  const [selectedVehicle, setSelectedVehicle] = useState("");

  // ========== FETCHING API DATA ==========
  useEffect(() => {
    async function getData() {
      const res = await axios.get(
        "https://612619c7e40e1900170727fe.mockapi.io/api/users"
      );
      setData(res.data);
    }
    getData();
  }, []);

  useEffect(() => {
    // If the state changes filter the results
    // Quick and dirty filter, you can improve on this
    if (selectedCountry || selectedVehicle) {
      const found = data.filter(
        (item) =>
          item.country === selectedCountry || item.vehicle === selectedVehicle
      );
      setFilteredData(found);
    } else {
      setFilteredData(data);
    }
  }, [data, selectedCountry, selectedVehicle]);

  return (
    <div className="app">
      <div className="top-section">
        <h1 className="top-title">Filter:</h1>
        {/* Pass the state as props to this child component */}
        <Dropdown
          data={data}
          setSelectedCountry={setSelectedCountry}
          setSelectedVehicle={setSelectedVehicle}
        />

        <div className="user-search">
          <Search data={data} />

          {/* Render the filtered data */}
          {filteredData.map((item) => (
            <div className="user-profile" key={item.id}>
              <img
                src="https://cdn.icon-icons.com/icons2/1378/PNG/512/avatardefault_92824.png"
                className="pfp"
                alt="user-avatar"
              />
              <p style={{ fontWeight: "bold" }}>{item.name}</p>
              <p style={{ opacity: "0.8" }}>{item.email}</p>
              <p>{item.country}</p>
              <p>{item.vehicle}</p>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

export default App;

Dropdown.js

This component takes data, selectedCountry and selectedVehicle App state as props, and set the values when an option is selected.

// Get the App state as props
function Dropdown({ data, setSelectedCountry, setSelectedVehicle }) {
  // FUNCTION TO RESET SEARCH RESULT
  function reset() {
    // Reset the state values
    setSelectedCountry("");
    setSelectedVehicle("");
  }

  // MAIN JSX
  return (
    <div className="dropdown">
      {/* LOADING DROPDOWN OPTIONS */}
      <div className="dropdown-options">
        <select
          className="dd-item"
          onChange={(e) => setSelectedCountry(e.target.value)}
        >
          <option value="">Country</option>
          {data.map((item) => (
            <option key={item.id} value={item.country}>
              {item.country}
            </option>
          ))}
        </select>

        <select
          className="dd-item"
          onChange={(e) => setSelectedVehicle(e.target.value)}
        >
          <option value="">Vehicle</option>
          {data.map((item) => (
            <option key={item.id} value={item.vehicle}>
              {item.vehicle}
            </option>
          ))}
        </select>

        <button className="reset" onClick={reset}>
          RESET
        </button>
      </div>
    </div>
  );
}

export default Dropdown;

Search.js

I didn't change much here, other than displaying the cards in the App component.

You don't need to fetch the data again, and pass it as a prop, like I did to the Dropdown component, but I'll leave it up to you to clean it up.

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

function Search() {
  const [searchData, setSearchData] = useState([]);
  const [input, setInput] = useState("");
  const [output, setOutput] = useState([]);

  // =============== GETTING API DATA ===============

  useEffect(() => {
    async function getData() {
      const res = await axios.get(
        "https://612619c7e40e1900170727fe.mockapi.io/api/users"
      );
      setSearchData(res.data);
      setOutput(res.data);
    }
    getData();
  }, []);

  // ========== MATCHING SEARCH INPUT WITH API DATA ==========

  useEffect(() => {
    setOutput([]);
    searchData.filter((item) => {
      if (
        item.name.toLowerCase().includes(input.toLowerCase()) ||
        item.country.toLowerCase().includes(input.toLowerCase()) ||
        item.email.toLowerCase().includes(input.toLowerCase()) ||
        item.vehicle.toLowerCase().includes(input.toLowerCase())
      ) {
        setOutput((output) => [...output, item]);
      }
    });
  }, [input]);

  function searchInput(e) {
    setInput(e.target.value);
  }

  return (
    <input
      className="searchbar"
      type="text"
      placeholder="SEARCH"
      onChange={searchInput}
    ></input>
  );
}

export default Search;
Related