26

using console.log() after using reactjs useState() hook, doesn't return the current value of this state, How can I handle this?

Here's code for the case, try to figure out what's the console log display.

import React, { useState } from "react";
import ReactDOM from "react-dom";

function Weather() {
  const [weather, setWeather] = useState();

  return (
    <input
      value={weather}
      onChange={(e) => {
        setWeather(e.target.value);
        console.log(weather);
      }}
    />
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Weather />, rootElement);
Zeyad Etman
  • 2,250
  • 5
  • 25
  • 42

3 Answers3

42

useState by default simply does one thing and one thing only, set the new state and cause a re-render of the function. It is asynchronous in nature so by default, methods running after it usually run.

From your example, on a fresh load of the page, typing 's' causes useState to change the state, but because it is asynchronous, console.log will be called with the old state value, i.e. undefined (since you didn't set a value. You should consider setting an initial state, if you want to)

const [weather, setWeather] = useState('');    // Set the intial state

The only way to truly read the value of the state is to use useEffect, which is called when there is a re-render of the component. Your method simply becomes:

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

function Weather() {
    const [weather, setWeather] = useState('');

    useEffect(() => console.log(weather), [weather]);

    const changeValue = event => setWeather(event.target.value);

    return <input value={weather} onChange={changeValue} />;
}

const rootElement = document.getElementById('root');
ReactDOM.render(<Weather />, rootElement);
cross19xx
  • 3,170
  • 1
  • 25
  • 40
  • 3
    you said "one thing and one thing only, set the new state and cause a re-render of the function." That is two things not one. I'm a bit confused. – LZ_ Apr 08 '21 at 15:13
  • i have tried this exact thing with my app using is there a reason it would be returning undefined? – Matt Laszcz Aug 11 '21 at 18:06
4

When you call setWeather, you trigger a rerender of your component, which will call again this function. However, the console.log(weather); inside the function still references the first value returned by useState, which is your orignal value. If you want to print it you should be doing :

console.log(e.target.value);
Vinz243
  • 9,654
  • 10
  • 42
  • 86
3
onChange={(e) => {
    setWeather(e.target.value);
    console.log(weather);
  }}

when this function is fired, since setWeather is async, it will be passed to web api. Javascript engine does not handle this function. wep api handles this and when it is done event loop which is part of the web api pass this to the call stack.

call stack is where functions get executed. once async functions are passed to the web api, regardless of its executing time (even though it has 0 second time out), they will not moved to call stack unless javascript engine finishes the execution of sync code. once call stack is free, event loop will pass the function to call stack to be executed.

So in your code, onChange() is passed to call stack. Inside of this function, 2 functions have to be executed. since setWeather is async, it goes to web api, gets the e.target.value, meanwhile console.log gets executed. when there is no sync code left in the call stack, event loop pushes the setWeather to the call stack to be executed.

Let's say you enter "name" to the input field. after "nam", state is "nam", but when you type "e", this will be executed in webapi, so while it is in web api, console.log will log the current state which is "nam". that is why you will always see the console one character behind

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
  • 1
    I liked your explanation but you should have provided a solution as well (I guess) ;) – GKV May 04 '22 at 04:19