Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 791
Sujit Avatar asked Sep 01 '20 05:09

Sujit


People also ask

What is setTimeout and how does it work?

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:

Is it possible to call setState in setTimeout?

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.

What if I’ve defined an alternative setTimeout method?

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.

What happens when setTimeout expires in JavaScript?

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?


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, []);
like image 69
CertainPerformance Avatar answered Sep 24 '22 14:09

CertainPerformance


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....
like image 32
francis duvivier Avatar answered Sep 24 '22 14:09

francis duvivier