In the below code snippet, the component is rendered thrice when user clicks on the button but the count values remain the same.
import React, { useEffect } from 'react';
const wait = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 2000);
});
export default function App() {
const [count, setCount] = React.useState(0);
useEffect(() => {
console.log('rerendered');
console.log(count);
});
const inc = async () => {
setCount(count 1);
setCount(count 5);
await wait();
console.log(count);
setCount(count 1);
console.log(count);
setCount(count 2);
console.log(count);
};
return (
<div>
<h1>{count}</h1>
<button onClick={inc}>Increment</button>
</div>
);
}
I know that first two setCount() are batched and after await they are not batched. If I use setCount(prev => prev 1), I will get the updated values but my question is
Why are the count values different in inc() and useEffect()? When does the count value actually changes coz certainly not on re-render?
CodePudding user response:
Short explanation:
The count value of 0 is closed over the inc function at the moment of execution (i.e. the moment the user pressed the Increment button). This is because the inc function opens a 
Explanation:
- "rerendered - useEffect
0":useEffectis called on mount of the component with the initial value0of thecountstate. - "rerendered - useEffect
5": Right after pressingIncrementthe two batchedsetCountcalls cause the value to be5and re-render. - Now the
incfunction waits for 2 seconds. - inc 1
0: After 2 seconds the async function continues its execution and this log is called. - inc 2
0: ThesetCountcall right after inc 10is called before thisconsole.logstatement, but executed asynchronously, so it will run after all synchronous tasks (this log), but before the next asynchronous task. - "rerendered - useEffect
1":setCountcaused a re-render.2is shown in the UI and theuseEffectis executed with this new value2. Why is the value not7(i.e.5 2)? - Because theincfunction closes over thecountvalue at the moment its called (i.e. when the user pressed the Increment button). Even newre-renderswon't alter thecountinside of the closure. - inc 4
0: Execution continues and the local "frozen"countvalue gets printed to the screen. - "rerendered - useEffect
1": Same explanation as point 6.
Play around with the code in this CodeSandBox
Also check out this great article about the useEffect hook which explains its relation with JavaScript closures really well.
