React Hooks State is not updated the first time

Viewed 696

Setting the state and the function which is updating the state:

const [objectivesOpen, setObjectivesOpen] = useState([]);
const toggleObjectives = () => {
  console.log('objectivesOpenNotUpdated', objectivesOpen);
  setObjectivesOpen([Math.random()]);
}

Calling the function:

<button onClick={toggleObjectives}>Test</button>

objectivesOpen is not getting the updated value after clicking the button the next time.

UPDATE: The value is updated in useEffect(), but not when clicking the button.

useEffect(() => {
  console.log('objectivesOpen in effect', objectivesOpen);
});

const toggleObjectives = () => {
  console.log('time:', new Date())
  console.log('objectivesOpen', objectivesOpen);

  setObjectivesOpen([Math.random()]);
};

Results:

time: Thu Dec 03 2020 17:17:58 GMT+0100 (Central European Standard Time)
objectivesOpen [0.4735904311779686]
objectivesOpen in effect [0.12100479433701583]
time: Thu Dec 03 2020 17:18:01 GMT+0100 (Central European Standard Time)
objectivesOpen [0.4735904311779686]
objectivesOpen in effect [0.6376121961469396]
2 Answers

setObjectivesOpen is the asynchronous method, and you can't get the updated objectivesOpen value right immediately after setObjectivesOpen.

You should use useEffect with adding an objectivesOpen dependency to get an updated objectivesOpen value.

useEffect(() => {
  console.log('objectivesOpenNotUpdated', objectivesOpen);
}, [objectivesOpen]);

The log inside the toggleObjectives can be confusing: that is in the middle of an update. What that log does is show you the current or old value, just before you change the value. So you only get to see the value AFTER you click "Change", but BEFORE the change is fully processed.

If you want to know the value of the objectivesOpen, I would advise to put the console log directly below the useState command. That is a more logical place.

Like so:

const [objectivesOpen, setObjectivesOpen] = useState([]);
console.log('objectivesOpen = ', objectivesOpen);

const toggleObjectives = () => {
  setObjectivesOpen([Math.random()]);
}

Then you get the value of objectivesOpen after each render. This is the value that is used in later code, e.g. if you want to display the objectivesOpen value to the user in a component.

If you put it here, you will get first [], the initial empty value. And then after each click, you get a new console log with the new value AFTER the state has been updated..

Maybe it is helpful to explain the what your code does.

  • On the first run, the value of objectivesOpen will be an empty array ([]), based on the first time you call useState([])
  • When the user clicks the function toggleObjectivesOpen(), the console should log this empty array
  • Then the asynchronous setObjectiveOpen() is called, passing in a new value of [Math.random()] (let's assume the value is [0.25]
  • It is possible that the user clicks the button again, before the state is updated. If that happens, the console should again show the old [] value of the state.
  • Or, if the user clicks after the state is updated, then the console shows the value after the first update ([0.25] from the example above).
  • After the state is updated, the code in the component will be run again. React will then ignore the useState command. But it will run the rest of the code (including all console.logs) and render all (child) components again.
Related