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
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.
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.
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 .
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.
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>
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With