React - useState - why setTimeout function does not have latest state value?
Solution 1:
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>
Solution 2:
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