useEffect(() => {
playLoop();
}, [state.playStatus]);
const playLoop = () => {
if (state.playStatus) {
setTimeout(() => {
console.log("Playing");
playLoop();
}, 2000);
} else {
console.log("Stopped");
return;
}
};
Output:
Stopped
// State Changed to true
Playing
Playing
Playing
Playing
// State Changed to false
Stopped
Playing // This is the problem, even the state is false this still goes on execute the Truthy stalemate
Playing
Playing
I am working on react-native and I want the recursion to stop when the state value becomes false. Is there any other way I can implement this code I just want to repeatedly execute a function while the state value is true. Thank you
setTimeout accepts a reference to a function as the first argument. This can be the name of a function: A variable that refers to a function (a function expression): Or an anonymous function: As noted above, it’s also possible to pass setTimeout a string of code for it to execute: However, this is not advisable for the following reasons:
Calling setState in setTimeout is therefore sync. I can confirm this behavior from my experience. This is not a bug, but it will probably change in the future (this was also mentioned in the issue I base my answer on). I am not part of the React team, correct me if I wrote something stupid.
If you’ve defined an alternative setTimeout method which would be found and returned in priority in the scope chain, then you’ve probably got bigger problems to worry about. For the purposes of this tutorial, I’ll omit window, but ultimately, which syntax you choose is up to you. setTimeout accepts a reference to a function as the first argument.
This is because when setTimeout ‘s timer has expired, the JavaScript engine places its callback function in a queue, behind the other console.log statements, to be executed. If you’d like to learn more about what happens when JavaScript runs, I highly recommend this video from JSConf 2014: What the heck is the event loop anyway?
Rather than having a playStatus
boolean, I'd save the interval ID. That way, instead of setting playStatus
to false
, call clearInterval
. Similarly, instead of setting playStatus
to true
, call setInterval
.
// Can't easily use useState here, because you want
// to be able to call clearInterval on the current interval's ID on unmount
// (and not on re-render) (interval ID can't be in an old state closure)
const intervalIdRef = useRef(-1);
const startLoop = () => {
// make sure this is not called while the prior interval is running
// or first call clearInterval(intervalIdRef.current)
intervalIdRef.current = setInterval(
() => { console.log('Playing'); },
2000
);
};
const stopLoop = () => {
clearInterval(intervalIdRef.current);
};
// When component unmounts, clean up the interval:
useEffect(() => stopLoop, []);
The first thing you should do is make sure to clear the timeout when the state changes to stopped or otherwise check the state within the timeout callback function.
But the problem does not seem to be with the setTimeout code only by itself, but rather that this playLoop is also being called too many times. You should add a console.log with a timestamp right at the start of your playLoop to confirm or disprove this. And to find out where it is called from, you could use console.trace.
const playLoop = () => {
console.log(new Date(), ': playLoop called')
console.trace(); // optional
if (state.playSt....
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With