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
":useEffect
is called on mount of the component with the initial value0
of thecount
state. - "rerendered - useEffect
5
": Right after pressingIncrement
the two batchedsetCount
calls cause the value to be5
and re-render. - Now the
inc
function waits for 2 seconds. - inc 1
0
: After 2 seconds the async function continues its execution and this log is called. - inc 2
0
: ThesetCount
call right after inc 10
is called before thisconsole.log
statement, but executed asynchronously, so it will run after all synchronous tasks (this log), but before the next asynchronous task. - "rerendered - useEffect
1
":setCount
caused a re-render.2
is shown in the UI and theuseEffect
is executed with this new value2
. Why is the value not7
(i.e.5 2
)? - Because theinc
function closes over thecount
value at the moment its called (i.e. when the user pressed the Increment button). Even newre-renders
won't alter thecount
inside of the closure. - inc 4
0
: Execution continues and the local "frozen"count
value 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.