Sorry for the confusing title, but here is what is going on:
In MyComponent
, I am setting a count
state with the useState
React hook.
Once the component mounts (ie. useEffect
with no dependencies), I am instantiaitng two MyClass
objects with the first argument as a callback function that increments the state, and the second argument is the timeOut
period to call the callback function.
The first instance of MyClass
calls the callback in 1000 ms and sets the new value for count
, which once updated, is logged in the second useEffect
.
However, when the second instance of MyClass
calls the call back (after timeOut
period of 3000 ms), and tries incrementing the count
value, it uses the state of count
from when the MyClass
was instantiated (which was 0), so it increments count
to 1 (wanted behavior is to increment to 2, since the first instance of MyClass
already incremented count
from 0 to 1)
This is not an issue related to the asynchronicity behavior of setState
because it is evident that that first update to count
happens before the second instance tries to update it again (the second useEffect
gets called when count
state is updated, which from the console log messages you can see is happening before second instance of MyClass
calls the call back).
JSFiddle link: https://jsfiddle.net/hfv24dpL/
So in conclusion, I think that the issue is that the count
state in the callback function is a copy of the count
state at the time when the callback functions were passed to the MyClass
constructor.
A solution to this example could be to just instantiate the second instance of MyClass
when the count
state is updated (in the second useEffect
), but this is not the solution I am looking for.
Another solution is to use setCount(prevCount => prevCount + 1)
to increment count
, but this isnt viable in my real application (MyComponent
and MyClass
are a skeleton example of my real React application that I wrote just for this question).
I want to be able to instantiate the classes togethor when component mounts (in first useEffect
), and have the callbacks refer to the most updated version of count
.
Is there a solution for this ^ or is there no way around this javascript and React implementation? Thanks for reading all this, I know its long :)
import React, { useState, useEffect } from 'react';
class MyClass{
constructor(callback, timeOut){
// call callback in timeOut milliseconds
this.timeOutId = setTimeout(() => {
callback();
}, timeOut)
}
clearTimeOut(){
clearTimeout(this.timeOutId);
}
}
function MyComponent(){
var [count, setCount] = useState(0);
// component did mount
useEffect(() => {
let myClass1 = new MyClass(funcToCallback, 1000);
let myClass2 = new MyClass(funcToCallback, 3000);
// when component unmounts, clear the timeouts of MyClass instances
return () => {
myClass1.clearTimeOut();
myClass2.clearTimeOut();
}
}, []);
// counter state updated
useEffect(() => {
console.log("COUNT UPDATED TO: ", count);
}, [count])
// get counter and increment it by 1
function funcToCallback(){
console.log("CALLBACK CALLED");
let newCount = count + 1;
incCount(newCount);
}
function incCount(newCount){
console.log("NEW COUNT: ", newCount);
setCount(newCount);
}
return (
<div>
COUNT: { count }
</div>
)
}