Search code examples
javascriptreactjsaxiosreact-hooksreact-functional-component

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();
    }, []);

Solution

  • 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()
      }, [])
    }