Filter components by the value of a select element

Here is a link to my app on Codesandbox https://codesandbox.io/s/filter-search-gxidc?file=/src/App.js it’s only one component app.js

In data.js I have an array of objects:

export const data = [
  {
    currencies: [{ code: "AFN", name: "Afghan afghani", symbol: "؋" }],
    languages: [
      { iso639_1: "ps", iso639_2: "pus", name: "Pashto", nativeName: "پښتو" },
      { iso639_1: "uz", iso639_2: "uzb", name: "Uzbek", nativeName: "Oʻzbek" },
      {
        iso639_1: "tk",
        iso639_2: "tuk",
        name: "Turkmen",
        nativeName: "Türkmen"
      }
    ],
    flag: "https://restcountries.eu/data/afg.svg",
    name: "Afghanistan",
    topLevelDomain: [".af"],
    capital: "Kabul",
    region: "Asia",
    subregion: "Southern Asia",
    population: 27657145,
    borders: ["IRN", "PAK", "TKM", "UZB", "TJK", "CHN"],
    nativeName: "افغانستان"
  },
  {
    currencies: [{ code: "EUR", name: "Euro", symbol: "€" }],
    languages: [
      {
        iso639_1: "sv",
        iso639_2: "swe",
        name: "Swedish",
        nativeName: "svenska"
      }
    ],
    flag: "https://restcountries.eu/data/ala.svg",
    name: "Åland Islands",
    topLevelDomain: [".ax"],
    capital: "Mariehamn",
    region: "Europe",
    subregion: "Northern Europe",
    population: 28875,
    borders: [],
    nativeName: "Åland"
  },
  {
    currencies: [{ code: "ALL", name: "Albanian lek", symbol: "L" }],
    languages: [
      { iso639_1: "sq", iso639_2: "sqi", name: "Albanian", nativeName: "Shqip" }
    ],
    flag: "https://restcountries.eu/data/alb.svg",
    name: "Albania",
    topLevelDomain: [".al"],
    capital: "Tirana",
    region: "Europe",
    subregion: "Southern Europe",
    population: 2886026,
    borders: ["MNE", "GRC", "MKD", "KOS"],
    nativeName: "Shqipëria"
  },
  {
    currencies: [{ code: "DZD", name: "Algerian dinar", symbol: "د.ج" }],
    languages: [
      { iso639_1: "ar", iso639_2: "ara", name: "Arabic", nativeName: "العربية" }
    ],
    flag: "https://restcountries.eu/data/dza.svg",
    name: "Algeria",
    topLevelDomain: [".dz"],
    capital: "Algiers",
    region: "Africa",
    subregion: "Northern Africa",
    population: 40400000,
    borders: ["TUN", "LBY", "NER", "ESH", "MRT", "MLI", "MAR"],
    nativeName: "الجزائر"
  },
  {
    currencies: [{ code: "USD", name: "United State Dollar", symbol: "$" }],
    languages: [
      {
        iso639_1: "en",
        iso639_2: "eng",
        name: "English",
        nativeName: "English"
      },
      {
        iso639_1: "sm",
        iso639_2: "smo",
        name: "Samoan",
        nativeName: "gagana fa'a Samoa"
      }
    ],
    flag: "https://restcountries.eu/data/asm.svg",
    name: "American Samoa",
    topLevelDomain: [".as"],
    capital: "Pago Pago",
    region: "Oceania",
    subregion: "Polynesia",
    population: 57100,
    borders: [],
    nativeName: "American Samoa"
  },
  {
    currencies: [{ code: "EUR", name: "Euro", symbol: "€" }],
    languages: [
      { iso639_1: "ca", iso639_2: "cat", name: "Catalan", nativeName: "català" }
    ],
    flag: "https://restcountries.eu/data/and.svg",
    name: "Andorra",
    topLevelDomain: [".ad"],
    capital: "Andorra la Vella",
    region: "Europe",
    subregion: "Southern Europe",
    population: 78014,
    borders: ["FRA", "ESP"],
    nativeName: "Andorra"
  },
  {
    currencies: [{ code: "AOA", name: "Angolan kwanza", symbol: "Kz" }],
    languages: [
      {
        iso639_1: "pt",
        iso639_2: "por",
        name: "Portuguese",
        nativeName: "Português"
      }
    ],
    flag: "https://restcountries.eu/data/ago.svg",
    name: "Angola",
    topLevelDomain: [".ao"],
    capital: "Luanda",
    region: "Africa",
    subregion: "Middle Africa",
    population: 25868000,
    borders: ["COG", "COD", "ZMB", "NAM"],
    nativeName: "Angola"
  }
];

I wanna map through it and display information about each country (I did this part ) at app.js

here is the code in app.js

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

export default function App() {
  const [cuntries, setCountries] = useState([]);
  const [region, setRegion] = useState('');

  useEffect(()=>{
    setCountries(data)
  },[])

  function filterByRegion(event) {
    setRegion(event.target.value);
    handleSubmit()
    // event.preventDefault()
  }

  function handleSubmit(){
    const newArr = cuntries.filter(cunt=> cunt.region === region)
    setCountries(newArr)
  }
  

  console.log(region);
  console.log(cuntries);

  return (
    <div className="App">
      <h1>filtering</h1>
      <form onChange={filterByRegion}>
      <label>Filter by region:</label>
        <select name="regions" id="region-select" >
          <option value="">Chose a region</option>
          <option value="Africa">Africa</option>
          <option value="Americas">Americas</option>
          <option value="Asia">Asia</option>
          <option value="Oceania">Oceania</option>
          <option value="Polar">Polar</option>
        </select>
      </form>
      <div>
        {cuntries.map((country) => {
          return (
            <div key={country.name} className="country">
              <h3>{country.name}</h3>
              <h3>{country.nativeName}</h3>
              <h3>{country.region}</h3>
            </div>
          );
        })}
      </div>
    </div>
  );
}

In app.js I have a select element that contains regions I wanna filter the array that I used to render each country So that when I choose a different region the map method render the countries that in this region

I tried some solution but non of them worked.

the link to the app again https://codesandbox.io/s/filter-search-gxidc?file=/src/App.js:0-1424

Answer

The main problem could be setRegion is handled asynchronously so once you try to filter further by its value in handleSubmit then the value is most probably not there. See down below the suggested modifications on your component.

The first thing is to add onChange event to select instead of form:

<select name="regions" id="region-select" onChange={filterByRegion}>
    <option value="">Chose a region</option>
    <option value="Africa">Africa</option>
    <option value="Americas">Americas</option>
    <option value="Asia">Asia</option>
    <option value="Oceania">Oceania</option>
    <option value="Polar">Polar</option>
</select>

Modify filterRegion to pass down the value to handleSubmit as:

const filterByRegion = (event) => {
    const region = event.target.value

    // I would keep this state if you really need for other purposes
    setRegion(region) 

    handleSubmit(region)
}

Then I would use with .filter the data as the original array to find values as:

const handleSubmit = (regionValue) => {
    const newArr = data.filter(c => c.region === regionValue)
    setCountries(newArr)
}

Please find the working CodeSandbox here.

+1 addition:

If you want to render something for no values found state for you countries:

{cuntries && cuntries.length > 0 ?
     cuntries.map((country) => {
          return (
            <div key={country.name} className="country">
              <h3>{country.name}</h3>
              <h3>{country.nativeName}</h3>
              <h3>{country.region}</h3>
            </div>
          );
     })
     : <div>No value found</div>
}

So if there are no values in your countries array you can render a <div> which states there are no values found.

Leave a Reply

Your email address will not be published. Required fields are marked *