Jack_T
Jack_T

Reputation: 125

React function running before state change

I have a function that is run when a user clicks a button, when this function is run it gathers data and updates state. I then have another function which runs that uses some of the data that is added to state, the issue is the state is not updating in time so the function is using old data.

First function

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);
    }
    const weatherDataResponse = await currentWeatherResponse.json();
    // Update state with data
    setWeatherData(weatherDataResponse);
  }

Second function

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);
  }

This then runs with the onClick calling both functions

  function callWeatherAndForcast() {
    callForcast();
    callWeather();
  }

Upvotes: 2

Views: 1501

Answers (4)

shukal chirag
shukal chirag

Reputation: 28

I think you should try to call callWeather(); under callForcast() after setForcastData() state set, and if update state value not affected in call weather you can try to add wait in setForcastData().

Or, try to add wait before callForcast() in callWeatherAndForcast() onClick

Upvotes: 1

Thomas Geenen
Thomas Geenen

Reputation: 113

Are you using FunctionComponent or Classes ?

Also, keep in mind that updating the state will trigger a rerendering. This means that:

  1. The state update is not immediate
  2. If one of your functions use the data from another, you should take care of these dependencies.

For helping you correctly, I need to know if you use FunctionComponent or Class and get the whole Function/Class.

Edit: based on the fact that you're using FunctionComponent.

In order to archive what you want, you need to use hooks.

Hooks are the way to handle a function component lifecycle.

For your problem, you'll need useState, useCallback hooks.


export const DisplayWeather = () => {

    const [forecast, setForecast] = useState();
    const [weather, setWeather] = useState();
    const [error, setError] = useState();

    const onSubmit = useCallback(async () => {
        getWeather();
        getForecast();
    }, [forecast, weather]);

    const getWeather = useCallback(async () => {
        const key = "";
        const location = formData.location;

        const currentWeatherURL = `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${key}`;
        const apiResponse = await fetch(currentWeatherURL);

        if(!apiResponse.ok){
            const message = `An error has occured: ${apiResponse.status}`;
            setError(message);
        } else {
            const weatherData = apiResponse.json();
            setWeather(weatherData);
        }
    }, [formData]);

    const getForecast = useCallback(async () => {
        const key = "";

        const lon = weather.coord.lon;
        const lat = weather.coord.lat;

        const forecastWeatherUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&units=metric&appid=${key}`
        const apiResponse = await fetch(forecastWeatherUrl);

        if(!apiResponse.ok) {
            const message = `An error has occured: ${apiResponse.status}`;
            setError(message);
        } else {
            const forecastData = apiResponse.json();
            setForecast(forecastData);
        }
    }, [weather]);

    if(error){
        return (
            <p>Error: {error}</p>
        )
    }
    
    return (
        <p>Forecast data</p>
        <p>{forecast.data.temperature}</p>
        <p>Weather data</p>
        <p>{weather.data.temperature}</p>
    );
}

In the code above, I set 2 state variables (weather & forecast) and create 3 functions.

The onSubmit function is called when the user click. His callback depend on two variables (weather & forecast) which are referenced in the dependency array (the [] after the callback)

The getWeather function is called before getForecast because the result of the getForecast function depends on the weather state. That's why you have weather in the getForecast callback dependency array. It tells getForecast that when the value of weather change, it needs to re-render.

Note that i've added formData into the dependency array of getWeather otherwise, when the user click, the getWeather function won't get any value from formData.

Note: it is not a working example, just a simple explanation. You can find more infos here: Hooks Reference useCallback Reference

Upvotes: 3

Jack_T
Jack_T

Reputation: 125

State does not update immediately! Meaning that the function I want to get the new state will get the previous state. To fix this I added callForcast function into a useEffect hook which has a dependency on callWeather because callForcast needs callWeather to update state first. This means when this function is run state will be updated in time.

      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);
        }
        // Call the callForcast function to run
        callForcast();
      },
    // This effect hook is dependent on callWeather
     [callWeather])

Now my onClick will only need to call callWeather() function.

Thanks to: @Mohammad Arasteh @Thomas Geenen @tromgy

Upvotes: 1

Mohammad Arasteh
Mohammad Arasteh

Reputation: 31

use 'await' before calling callForcast so the second function (callWeather) does'nt get called immediately after calling first function.

  async function callWeatherAndForcast() {
    await callForcast();
    callWeather();
  }

also as @tromgy mentioned in the comments, React state updates are not immediate, try calling callWeather function inside a hook which has a dependency on forcastData state

Upvotes: 3

Related Questions