Search code examples
reactjsreact-hooksuse-effectuse-state

React useState hook updating only after second click


In a child component called Cities I have a button with an onClick event handler that updates the state of 'worldCity' to 'London' and then another button updating the state to 'Paris'. The problem is that the city only has the state updated when the button is clicked twice. I've read that it's because updating state is asynchronous and that useEffect is the solution for this. But what I've tried still results in the state only being updated after two button clicks.

In the Cities child component (which exists on its own separate page)

  const [worldCity, setWorldCity] = useState('')

  <button onClick={() => cityProp(setWorldCity('London'))}>
    London
  </button>
  <button onClick={() => cityProp(setWorldCity('Paris'))}>
    London
  </button>

In the parent component:

const Homepage = () => {
  const {
    worldCity,
    setWorldCity,
  } = WeatherState() // WeatherState() here is a context API where the useState hook is available throughout the application

The function to call the api with the updated city:

// Fetch City by name API call
  const fetchCity = async (worldCity) => {
    setLoading(true)
    const { data } = await axios.get(WeatherAPI(worldCity, days))

    setWeather(data)
    setLoading(false)

    console.log('fetchCity called and city is:', worldCity)
  }

The use effect hook (which resides in the parent component):

  useEffect(() => {
    console.log('World city updated', worldCity)
  }, [worldCity])

and the child component that calls the fetchCity function with the 'worldCity' variable from the original button click in the child component.

<CityWeather cityProp={() => fetchCity(worldCity)} />

Solution

  • You are correct. setState is async, and you are trying to fetch weather without waiting for that setState to finish.

    
      <button onClick={() => {
    cityProp("london")
     setWorldCity("london") //<--- Will resolve later, useEffect with catch the result.
    }>
    

    You can also do this:

      <button onClick={() => {
    const newCity = "london" //<--- newCity is now the current city. Use this inside of this function as worldCity will be stale until this function finishes.
    cityProp(newCity)
     setWorldCity(newCity) 
    }>