"use client"
import React, { useEffect } from 'react'
import { useState } from 'react';
export default function page() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((count)=>count + 1);
};
function checkVal(){
console.log(count)
}
useEffect(()=>{
setInterval(checkVal,1000)
}
,[])
return (
<div>
<button onClick={handleClick}>{count}</button>
<button onClick={checkVal}>clickmetocheck</button>
</div>
)
}
I have a following code which checks the value of count when
- checkVal is called through setInterval
- checkVal is called through onClick event
Since these two condition is calling the same function checkVal(), I was expecting console to print the same number. However, checkVal through setInterval was only printing the default value even when the value was updated through handleClick(). And checkVal through onclick event was correctly printing out count.
Same code, different behaviour. Why does this happen?
2
Answers
The same code when called from different contexts may behave differently.
Context 1 : onClcik event
Context 2 : useEffect event
The most significant similarity in the above two contexts is, both are events, and events will have visibility of state with respect to latest render. Therefore the value of the state count visible in both contexts is the same.
However, Context 2 has a nested context which is a setInterval. setInterval when invoked does only one thing : It just schedules the given code for execution. The below code will be scheduled to execute on every second.
Now let us examine the above code.
It is a function nested inside the higher order function page. Since it is a nested function, it can access the variables from its enclosing function.
Now comes the most important point : When the nested function had been created while rendering the component page, its binding to the variables in its enclosing function has also been created. Therefore it is not only a function object created but a scope as well. The variables referenced inside the nested function are resolved from this scope. This mechanism – a function object along with its scope is generally called closure.
Now the point of failure is this:
Since no further changes are happening on any of the three steps above, when the following code will run on its due time, its variable count will always be resolved from the same scope with the same value 0. Therefore it prints 0 always.
You can run the below code to test and confirm this understanding in another way.
Try the below code – adding count as its dependency.
Though the below code will work infinite, it will still print the latest count based on the button clicking. The above three points will change on every mouse click. UseEffect will fire again, setInterval will be invoked again, and the nested function will be re-created with a new scope every time. Therefore the variable reference will be get the latest value while printing in console.
The issue here is that you never update the useEffect, so the
checkVal
function is using an oldcount
. To solve it you need to review how effects dependencies work in React.For this particular example, you could update the
useEffect
as follows:checkVal
as dependency of the effectcheckVal
function changes to avoid creating new intervals on every effect change.That will work, however, it is not a good idea to add a dependency on your effect that is changing on every render. That is because your
checkVal
function is not memoized and it will be re-created on every single render.You could improve your code as follows:
setInterval
, do not bind the reference.This creates a single function that is bind to the setInterval. However, on every call, it will trigger the latest function stored in the reference that will be always updated.