Search code examples
reactjsasynchronouspromiseaxiosuse-effect

Why is React's useEffect hook not called here before the function returns?


The useEffect hook below should set the weather variable, but it is not doing so -- instead, the React component returns before the hook is called.

Why is the useEffect hook not called before the React component returns, and how can I fix this problem?

// A React component that shows a given city's weather.
const CityWeather = ({ city }) => {
  const [weather, setWeather] = useState({})
 
  useEffect(() => {
    axios
      .get(`http://api.weatherapi.com/v1/current.json` +
        `?key=${process.env.REACT_APP_MY_SUPER_SECRET_WEATHER_API_KEY}` +
        `&q=${city}`)
      .then(response => {
        console.log(`response is ${JSON.stringify(response)}`)
        setWeather(response.data)})
  },
  [city])

  console.log(`The value of the weather var is ${JSON.stringify(weather)}`)
  return (
    <div>
      <h1>Weather in {city}</h2>
      <p>temperature: {weather.current.temp_c} Celsius</p>
      <img src={weather.current.condition.icon} alt="weather forecast"/>
      <p>wind speed: {weather.current.wind_mph} mph</p>
      <p>wind dir.: {weather.current.wind_dir}</p>
    </div>
  )
}

Solution

  • This is because the call to the api is asynchronous, which means that the code will continue to execute whilst the fetch happens in the background, when a response is returned it will then callback in the .then() part of the effect.

    A common practice is to show a placeholder whilst loading data:

    const CityWeather = ({ city }) => {
      const [weather, setWeather] = useState(null);
    
      useEffect(() => {
        axios
          .get(
            `http://api.weatherapi.com/v1/current.json` +
              `?key=${process.env.REACT_APP_MY_SUPER_SECRET_WEATHER_API_KEY}` +
              `&q=${city}`,
          )
          .then((response) => {
            console.log(`response is ${JSON.stringify(response)}`);
            setWeather(response.data);
          });
      }, [city]);
    
      console.log(`The value of the weather var is ${JSON.stringify(weather)}`);
      return (
        <div>
          {!weather ? (
            <h3>Loading the weather!</h3>
          ) : (
            <>
              <h2>Weather in {city}</h2>
              <p>temperature: {weather.current.temp_c} Celsius</p>
              <img src={weather.current.condition.icon} alt="weather forecast" />
              <p>wind speed: {weather.current.wind_mph} mph</p>
              <p>wind dir.: {weather.current.wind_dir}</p>
            </>
          )}
        </div>
      );
    };
    

    (Also, I noticed you defaulted weather to an array ([]) which is probably causing other issues)