1

im trying to build a weather app and i have been defined buttons to change the city name state and fetch the new city weather info by changing the dependency list of the useEffect hook and console log it for checking frst. but the problem is that the state is updating two steps behind the buttons that i click.

Main component for states and fetching data:

function App() {

  const [cityName, setCityName] = useState('Toronto')
  const [weather, setWeather] = useState('')
  const url = `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&units=metric&appid=5476766e86450fff39da1502218a3376`

  const getData = () => {
    axios.get(url).then((res) => {
      setWeather(res.data);
    });
    console.log(weather)
  }

  useEffect(()=>{
    getData()
  },[])

  return <div className="bg-slate-600">
    <Topbuttons setCityName={setCityName} getData={getData} />
    <Inputs setCityName={setCityName} getData={getData} />
    <Data weather={weather} />
  </div>;
}

export default App;

Im sending the setCity to the buttons component as a prop. nevermind the getData() being repeated in useEffect its just for test

buttons component:

function Topbuttons({setCityName,getData}) {
    const cities = ['Tehran', 'Paris', 'Newyork', 'Berlin', 'London']
    return (
        <div className='flex mx-auto py-3 justify-around space-x-8 items-center text-white text-l font-bold'>
            {cities.map((v,i) => {
                return <button
                key={i}
                onClick={()=>{
                    console.log(v)
                    setCityName(v)
                    getData()
                    }}>
                    {v}
                </button>
            })}
        </div>
    )
}

export default Topbuttons

thanks for your help

Amir-H
  • 53
  • 4
  • Does this answer your question? [The useState set method is not reflecting a change immediately](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately) – David Feb 16 '23 at 17:04
  • 1
    The issue in the linked duplicate is happening a few times in the code shown, but it all seems to come down to that same issue. Get rid of `console.log(weather)`, it serves no purpose. Then don't call `getData()` from the JSX, but do add `cityName` to the dependency array of the `useEffect` so it gets called whenever the city changes. Then all you ever need to do in the click handler is call `setCityName(v)`. – David Feb 16 '23 at 17:07
  • The issue is with the order in which you are calling `setCityName` (`sync`) and `setCityName` (`async`). Provided an example in the answer. – Wesley LeMahieu Feb 16 '23 at 19:21

1 Answers1

0

The problem is that you run setCityName() immediately on click but you run setWeather() after the promise is complete with data. One way to sync them is to run them at the same time after the async promise completes.

Here's a working sandbox of your code. Try using this setup:

app.js

import axios from "axios";
import { useEffect, useState } from "react";
import TopButtons from "./TopButtons";

function App() {
  const [cityName, setCityName] = useState("Toronto");
  const [weather, setWeather] = useState("");

  const getData = (newCityName) => {
    console.log("getData newCityName", newCityName);
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${newCityName}&units=metric&appid=5476766e86450fff39da1502218a3376`;
    axios.get(url).then((res) => {
      setCityName(newCityName);
      setWeather(res.data);
    });
  };

  useEffect(() => {
    getData();
  }, []);

  return (
    <div className="bg-slate-600">
      <TopButtons setCityName={setCityName} getData={getData} />
      <span>{JSON.stringify(weather)}</span>
    </div>
  );
}

export default App;

TopButtons.js

function Topbuttons({ getData }) {
  const onClick = (newCityName) => getData(newCityName);

  const cities = ["Tehran", "Paris", "Newyork", "Berlin", "London"];

  return (
    <div className="flex mx-auto py-3 justify-around space-x-8 items-center text-white text-l font-bold">
      {cities.map((v, i) => {
        return (
          <button key={i} onClick={() => onClick(v)}>
            {v}
          </button>
        );
      })}
    </div>
  );
}

export default Topbuttons;

The next step would be to detect for a loading state. Since you're using Axios + React, I recommend using the axios-hooks library which uses Axios under-the-hood but provides loading state in hook-format which makes API calls easier to deal with.

Wesley LeMahieu
  • 2,296
  • 1
  • 12
  • 8