Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - useState - why setTimeout function does not have latest state value?

Recently I was working on React Hooks and got stuck with one problem/doubt?

Below is a basic implementation to reproduce the issue, Here I'm just toggling flag (a state) variable on click of the button.

  const [flag, toggleFlag] = useState(false);   const data = useRef(null);   data.current = flag;    const _onClick = () => {     toggleFlag(!flag);     // toggleFlag(!data.current); // working      setTimeout(() => {       toggleFlag(!flag); // does not have latest value, why ?       // toggleFlag(!data.current); // working     }, 2000);   };    return (     <div className="App">       <button onClick={_onClick}>{flag ? "true" : "false"}</button>     </div>   ); 

I figured out some other way to overcome this problem like using useRef or useReducer, but is this correct or is there any other way to solve this with useState only?

Also, it would be really helpful if anyone explains why we get old value of state inside the setTimeout.

Sandbox URL - https://codesandbox.io/s/xp540ynomo

like image 470
manish keer Avatar asked Mar 16 '19 15:03

manish keer


People also ask

Why setTimeout function does not have latest state value?

This boils down to how closures work in JavaScript. The function given to setTimeout will get the flag variable from the initial render, since flag is not mutated.

Why React useState setState does not update immediately?

If you find that useState / setState are not updating immediately, the answer is simple: they're just queues. React useState and setState don't make changes directly to the state object; they create queues to optimize performance, which is why the changes don't update immediately.

How do you access the state in setTimeout inside a React function component?

For changing the state in such a case just use the "setState” function and provide an updater callback. If for example, we want to check the state after 5 seconds and if the counter is lower than 5 to make it 5 we can do that test condition inside the updater function pass to the setCount .

How does React useState know what the current state is?

React resets the current Hook variable and calls your component. React finds a call to useState , but this time, since there's already a Hook at the first position of the list of Hooks, it just changes the current Hook variable and returns the array with the current state and the function to update it.


2 Answers

This boils down to how closures work in JavaScript. The function given to setTimeout will get the flag variable from the initial render, since flag is not mutated.

You could instead give a function as argument to toggleFlag. This function will get the correct flag value as argument, and what is returned from this function is what will replace the state.

Example

const { useState } = React;    function App() {    const [flag, toggleFlag] = useState(false);      const _onClick = () => {      toggleFlag(!flag);        setTimeout(() => {        toggleFlag(flag => !flag)      }, 2000);    };      return (      <div className="App">        <button onClick={_onClick}>{flag ? "true" : "false"}</button>      </div>    );  }    ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>    <div id="root"></div>
like image 110
Tholle Avatar answered Sep 18 '22 08:09

Tholle


The function given to setTimeout will get the flag variable from the _onClick function. The _onClick function gets created every render and "stores" the value which the flag variable gets on this render.

function App() {   const [flag, toggleFlag] = useState(false);   console.log("App thinks that flag is", flag);    const _onClick = () => {     console.log("_onClick thinks that flag is", flag);     toggleFlag(!flag);      setTimeout(() => {       console.log("setTimeout thinks that flag is", flag);     }, 100);   };    return (     <div className="App">       <button onClick={_onClick}>{flag ? "true" : "false"}</button>     </div>   ); } 

Console:

App thinks that flag is false  _onClick thinks that flag is false App thinks that flag is true setTimeout thinks that flag is false  _onClick thinks that flag is true App thinks that flag is false setTimeout thinks that flag is true 
like image 24
UjinT34 Avatar answered Sep 21 '22 08:09

UjinT34