setTimeout() function is not detecting a state change and keeps executing the recursive function

Viewed 787
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

2 Answers

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....
Related