0

The effect ends up in an endless re-render loop, it keeps making requests to the url. Basically what I have is an API call to fetch countries. Then based on a filter input, it renders the countries that match the search. If there is only one result, it should also fetch and display weather information of the capital.

The only time I want to re-render the weather is when the value of the country changes. Somehow I can't get it to work..

let filtered = countries.filter(country => country.name.toUpperCase().includes(filter.toUpperCase()))

const [weather, setWeather] = useState([])

useEffect(() => {
  if (filtered.length === 1) {
    const country = filtered[0]
    const api_key = process.env.REACT_APP_API_KEY
    const url = `http://api.weatherstack.com/current?access_key=${api_key}&query=${country.capital}`
    axios.get(url)
      .then(response => {
        setWeather(response.data.current)
      })
  }

}, [filtered])

1 Answer 1

1

Whenever the component is rendered, filtered (the dependency) is recreated, which means that useEffect is invoked, making the API all, which sets the state, and causes rerender, recreating filtered...

To prevent that, wrap the filtered expression with useMemo. The useMemo block should depend on countries and filter. As long as countries or filter don't change filtered would stay the same, preventing the loop. If one of them changes, filtered would change, and the useEffect will call the API again with the new data.

let filtered = useMemo(() => countries.filter(country =>
  country.name.toUpperCase().includes(filter.toUpperCase()),
[countries, filter])

Another option is use Array.find() instead of filter, and make useEffect() dependent on the country found:

const country = useMemo(() => countries.find(country =>
  country.name.toUpperCase().includes(filter.toUpperCase())
), [countries, filter])

const [weather, setWeather] = useState([])

useEffect(() => {
  if (country) {
    const api_key = process.env.REACT_APP_API_KEY
    const url = `http://api.weatherstack.com/current?access_key=${api_key}&query=${country.capital}`
    axios.get(url)
      .then(response => {
        setWeather(response.data.current)
      })
  }

}, [country])
Sign up to request clarification or add additional context in comments.

5 Comments

You might want to update that dependency for filtered to include filter as well since that has a huge affect on the value of filtered.
Good catch. Missed it. Updating.
That's it! Thanks for your great explanation!
@OriDrori It is out of the loop now, At the moment it also makes API calls whenever filter is updated but when country stays the same. Any idea on how I could solve this?
Filter actually changes the array, so there's no escape from using it as dependency. However, I'll add another option since you can make it dependant on the capsule found, instead of the entire array.

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.