zer0day
zer0day

Reputation: 246

how to stop multiple re-renders from doing multiple api calls useEffect?

I'm new to react functional components and I'm trying to get the weather data on multiple cities on page load but useEffect is now re-rending each call. How can I write this so useEffect doesn't cause re-renders?

function App() {
    const [data, setData] = useState([]);
    const [activeWeather, setActiveWeather] = useState([]);

    useEffect(() => {
        const key = process.env.REACT_APP_API_KEY;

        const fetchData = async (city) => {
            const res = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}`);
            setData((data) => [
                ...data,
                { description: res.data.weather[0].description, icon: res.data.weather[0].icon, temp: res.data.main.temp, city: res.data.name, country: res.data.sys.country, id: res.data.id },
            ]);
        };
        const fetchCities = () => {
            const cities = [fetchData("Ottawa"), fetchData("Toronto"), fetchData("Vancouver"), fetchData("California"), fetchData("London")];

            Promise.all(cities).catch((err) => {
                console.log(err);
            });
        };
        fetchCities();
    }, []);

Upvotes: 5

Views: 16530

Answers (2)

Asaf Aviv
Asaf Aviv

Reputation: 11770

You can make the fetchData function to return the data you need without updating the state, then you can fetch x amount of cities and only when all of the requests complete update the state.

Note that if one of the requests inside Promise.all fail, it will go to the catch block without returning any data back, basically all or nothing

const key = process.env.REACT_APP_API_KEY

const fetchCity = async city => {
  const { data } = await axios.get(
    `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}`,
  )

  return {
    description: data.weather[0].description,
    icon: data.weather[0].icon,
    temp: data.main.temp,
    city: data.name,
    country: data.sys.country,
    id: data.id,
  }
}

function App() {
  const [cities, setCities] = useState([])
  const [activeWeather, setActiveWeather] = useState([])

  useEffect(() => {
    const fetchCities = async () => {
      const citiesData = await Promise.all(
        ['Ottawa', 'Toronto', 'Vancouver'].map(fetchCity),
      )

      setCities(prevState => prevState.concat(citiesData))
    }

    fetchCities()
  }, [])
}

Upvotes: 5

Dvir Hazout
Dvir Hazout

Reputation: 261

You can use Promise.all and then call setData once. something like this:

useEffect(() => {
  const fetchCity = (city) => axios.get(`${base}/${city}`);
  const cities = ["Ottawa", "Toronto"];
  const promises = cities.map(fetchCity);
  Promise.all(promises).then((responses) => {
    setData(cities.map((city, index) => ({ city, ...responses[index] })));
  });
}, []);

Upvotes: 2

Related Questions