variable in useState not updating in useEffect callback
There are a couple of issues:
- You're not returning a function from
useEffect
to clear the interval - Your
inc
value is out of sync because you're not using the previous value ofinc
.
One option:
const counter = ({ count, speed }) => {
const [inc, setInc] = useState(0);
useEffect(() => {
const counterInterval = setInterval(() => {
setInc(inc => {
if(inc < count){
return inc + 1;
}else{
// Make sure to clear the interval in the else case, or
// it will keep running (even though you don't see it)
clearInterval(counterInterval);
return inc;
}
});
}, speed);
// Clear the interval every time `useEffect` runs
return () => clearInterval(counterInterval);
}, [count, speed]);
return inc;
}
Another option is to include inc
in the deps array, this makes things simpler since you don't need to use the previous inc
inside setInc
:
const counter = ({ count, speed }) => {
const [inc, setInc] = useState(0);
useEffect(() => {
const counterInterval = setInterval(() => {
if(inc < count){
return setInc(inc + 1);
}else{
// Make sure to clear your interval in the else case,
// or it will keep running (even though you don't see it)
clearInterval(counterInterval);
}
}, speed);
// Clear the interval every time `useEffect` runs
return () => clearInterval(counterInterval);
}, [count, speed, inc]);
return inc;
}
There's even a third way that's even simpler:
Include inc
in the deps array and if inc >= count
, return early before calling setInterval
:
const [inc, setInc] = useState(0);
useEffect(() => {
if (inc >= count) return;
const counterInterval = setInterval(() => {
setInc(inc + 1);
}, speed);
return () => clearInterval(counterInterval);
}, [count, speed, inc]);
return inc;
The issue here is that the callback from clearInterval
is defined every time useEffect
runs, which is when count
updates. The value inc
had when defined is the one that will be read in the callback.
This edit has a different approach. We include a ref to keep track of inc
being less than count
, if it is less we can continue incrementing inc
. If it is not, then we clear the counter (as you had in the question). Every time inc
updates, we evaluate if it is still lesser than count and save it in the ref
. This value is then used in the previous useEffect
.
I included a dependency to speed
as @DennisVash correctly indicates in his answer.
const useCounter = ({ count, speed }) => {
const [inc, setInc] = useState(0);
const inc_lt_count = useRef(inc < count);
useEffect(() => {
const counterInterval = setInterval(() => {
if (inc_lt_count.current) {
setInc(inc => inc + 1);
} else {
clearInterval(counterInterval);
}
}, speed);
return () => clearInterval(counterInterval);
}, [count, speed]);
useEffect(() => {
if (inc < count) {
inc_lt_count.current = true;
} else {
inc_lt_count.current = false;
}
}, [inc, count]);
return inc;
};