Home > Blockchain >  Run a function inside useEffect only once
Run a function inside useEffect only once

Time:12-16

So I have a useEffect hook that has a function called callForcast() this has a dependency of callWeather so when the callWeather() function is run callForcast() runs. callweather() is called using an onClick and the callForcast() I added a call inside the useEffect() but this causes an infinite loop.

How can I make this function run once.

  useEffect (() => {
    async function callForcast() {
      const key = "";
      // Get lat & lon from the previous data fetch
      const lon = weatherData.coord.lon
      const lat = weatherData.coord.lat
      // Get forcast data
      const forcastWeatherUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&units=metric&appid=${key}`
      const forcastWeatherResponse = await fetch(forcastWeatherUrl);
      if (!forcastWeatherResponse.ok) {
        const message = `An error has occured: ${forcastWeatherResponse.status}`;
        throw new Error(message);
      } 
      const forcastDataResponse = await forcastWeatherResponse.json();
      // Update state with the forcast data
      setForcastData(forcastDataResponse);
    }
    // Causing infinite loop
    callForcast(); 

  }, [callWeather])

This is what the callForcast function relies on

  async function callWeather() {
    const key = "";
    // Get location by user
    let location = formData.location;
    // Url for current weather
    const currentWeatherUrl = `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${key}`;
    // Get the current weather
    const currentWeatherResponse = await fetch(currentWeatherUrl);
    if (!currentWeatherResponse.ok) {
      // Return this message if an error
      const message = `An error has occured: ${currentWeatherResponse.status}`;
      throw new Error(message);
    }
    // Get data if no error
    const weatherDataResponse = await currentWeatherResponse.json();
    // Update state with data
    setWeatherData(weatherDataResponse);
  }

CodePudding user response:

Why does this have a dependency on callWeather? It doesn't use callWeather.

It's executing over and over presumably because whatever callWeather is, it's changing on each render. (Maybe being re-declared and/or re-assigned on each render.)

If this effect should only execute once, use an empty dependency array:

useEffect(() => {
  // the code in the effect
}, []);

This will execute the effect only once when the component first loads.


Alternatively...

when the callWeather() function is run callForcast() runs

If you want to invoke callForcast() whenever you invoke callWeather() then useEffect is the wrong tool. Just define the function you want to call and call it when you want to call it. For example:

const callForcast = () => {
  // the code in the function
};

const callWeather = () => {
  // the code in the function
  
  callForcast();
};

Another possibility...

If you have a state value that is a dependency and want to invoke the effect any time that dependency changes, use that state value as the dependency:

useEffect(() => {
  // the code in the effect
}, [weatherData]);

Or...

If this function just needs some data provided to it from another operation, provide that data. For example:

const callForcast = (dataNeeded) => {
  // the code in the function
};

const callWeather = () => {
  // the code in the function
  
  callForcast(someData);
};

Overall it's not entirely clear under what circumstances you want to execute callForcast. (Though you may be clarifying that in the question as I type this.) But in your original code the reason it executes on every render is because callWeather is changing on every render and is surely the wrong dependency to use.

CodePudding user response:

Try actually calling callWeather in your effect. and if it is async you can call it within callForcast

useEffect (() => {
    // callWeather();

    async function callForcast() {
      await callWeather();

      const key = "";
      // Get lat & lon from the previous data fetch
      const lon = weatherData.coord.lon
      const lat = weatherData.coord.lat
      // Get forcast data
      const forcastWeatherUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&units=metric&appid=${key}`
      const forcastWeatherResponse = await fetch(forcastWeatherUrl);
      if (!forcastWeatherResponse.ok) {
        const message = `An error has occured: ${forcastWeatherResponse.status}`;
        throw new Error(message);
      } 
      const forcastDataResponse = await forcastWeatherResponse.json();
      // Update state with the forcast data
      setForcastData(forcastDataResponse);
    }
    // Causing infinite loop
    callForcast(); 

  }, [callWeather])

furthermore you should just have callWeather return your data in a promise so you can extract it in your callForcast fn. like const data = await callWeather();

Edit: seeing the updates you probably just want

  // will run callForcast every time 
  useEffect (() => {
    async function callForcast() {
       ...
    }
    callForcast(); 
  }, [weatherData]);

OR

  async function callWeather() {
    ...
    setWeatherData(weatherDataResponse);
    return weatherDataResponse;
  }

  useEffect (() => {
    async function callForcast() {
       const weatherData = await callWeather();
       ...
    }
    callForcast(); 
  }, [callWeather]);
  • Related