0
import React, { useState, useEffect } from "react";
import axios from "axios";

const App = () => {
  let [countries, setCountries] = useState([]);
  const [newCountry, newStuff] = useState("");
  const hook = () => {
    //console.log("effect");
    axios.get("https://restcountries.eu/rest/v2/all").then((response) => {
      console.log("promise fulfilled");
      setCountries(response.data);
      //console.log(response.data);
    });
  };

  const filter = (event) => {
    newStuff(event.target.value);

    if (event.target.value === undefined) {
      return
    } else {
      let value = event.target.value;
      console.log(value);
      countries = countries.filter((country) => country.name.startsWith(value));
      setCountries(countries);
      console.log(countries);
    }
  };
  useEffect(hook, []);
  return (
    <div>
      <p>find countries</p>
      <input value={newCountry} onChange={filter} />
      <ul>
        {countries.map((country) => (
          <li key={country.name.length}>{country.name}</li>
        ))}
      </ul>
    </div>
  );
};
export default App;

So I have a search bar so that when you enter a few characters it will update the state and show the countries that start with the respective first characters. However, nothing is being shown when I enter input into my search bar. Also, my filter function, when I console.log my countries array which is supposed to have the countries that start with the characters I entered, it's always an empty array.

2
  • You need to wrap your hook into async useCallback. And you are not able to mutate state countries. Use immutable way to update your state. Commented Jun 24, 2020 at 15:38
  • Do I just need to put await? Commented Jun 24, 2020 at 15:39

2 Answers 2

1

You need some changes in order to make this work:

  1. Use two states for countries, one for the list you get in the initial render and another for the current filter countries.

    const [countriesStore, setCountriesStore] = useState([]); // this only change in the first render
    const [countries, setCountries] = useState([]); // use this to print the list
    

    I recomed to use any tool to manage the state and create a model for the countries ther you can make the side effect there and create an action that update the countries store. I'm using Easy Peasy in my current project and it goes very well.

  2. Take care of the filter method because startsWith method is not case-insensitive. You need a regular expression or turn the current country value to lower case. I recommend to use includes method to match seconds names like island in the search.

    const filterCountries = countriesStore.filter(country => {
          return country.name.toLowerCase().includes(value);
     });
    
  3. Remove the if condition in the filter in order to include the delete action in the search and get the full list again if everything is removed.

  4. Just in the case, empty the search string state in the first render

    useEffect(() => { hook(); setSearchString(""); }, []);

  5. Replace the length in the list key. You can use the name and trim to remove space.

    <li key={country.name.trim()}>{country.name}</li>

The final code look like this:

export default function App() {
  const [countriesStore, setCountriesStore] = useState([]);
  const [countries, setCountries] = useState([]);
  const [searchString, setSearchString] = useState("");

  const hook = () => {
    axios.get("https://restcountries.eu/rest/v2/all").then(response => {
      console.log("promise fulfilled");
      setCountriesStore(response.data);
      setCountries(response.data);
    });
  };

  const filter = event => {
    setSearchString(event.target.value);

    let value = event.target.value;

    const filterCountries = countriesStore.filter(country => {
      return country.name.toLowerCase().includes(value);
    });

    setCountries(filterCountries);
  };

  useEffect(() => {
    hook();
    setSearchString("");
  }, []);

  return (
    <div>
      <p>find countries</p>
      <input value={searchString} onChange={filter} />
      <ul>
        {countries.map(country => (
          <li key={country.name.trim()}>{country.name}</li>
        ))}
      </ul>
    </div>
  );
}
Sign up to request clarification or add additional context in comments.

Comments

0

You need to wrap your hook into async useCallback:

const hook = useCallback(async () => {
    const {data} = await axios.get("https://restcountries.eu/rest/v2/all");
    setCountries(data);
}, []);

you are not able to mutate state countries. Use immutable way to update your state:

const filter = (event) => {
    newStuff(event.target.value);

    if (event.target.value === undefined) {
      return
    } else {
      let value = event.target.value;
      setCountries(countries.filter((country) => country.name.startsWith(value)));
    }
};

And useState is asynchronous function. You will not see result immediately. Just try to console.log outside of any function.

1 Comment

So when would I see the result? I console.log outside of filter function but my array still has nothing

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.