Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

276
Views
Start and Stop a Timer with Recursive setTimeout

I am trying to start and stop a timer with a recursive setTimeout, since setInterval doesn't fit my need. When the user presses start, the timer starts counting.

1...2...3...4..5...1...2...3..4...5...1..2...3..4...5...

And when the user presses stop, it should stop this timer loop. If I press start again, changing the state of the button (stream variable), the timer should do the same thing again (1..2..3..4..5..1..2...3..4...5..)

I thought the way to do it would be this.


const ref = useRef(null);
useEffect(() => {
  if (!stream) {
    ref.current=setTimeout(repeatingFunc, 1000);

  } else {
    clearTimeout(ref.current);
  }
}, [stream]);

function repeatingFunc() {
  console.log("It's been 5 seconds. Execute the function again.");
  setTimeout(repeatingFunc, 5000);
}

What am I doing wrong?

The timer doesn't stop. It keeps going even if I press stop! (changing the stream state value!)

about 4 years ago · Juan Pablo Isaza
2 answers
Answer question

0

You can define a custom timer like this:

function CustomTimer(func) {
    this.id = -1;
    this.func = func;

    this.start = function (interval) {
        this.stop();

        if (this.func) {
            var t = this;
            this.id = setTimeout(function () { t.func.call(t) }, interval || 1000);
        }
    }

    this.stop =  function () {
        if (this.id >= 0) {
            clearTimeout(this.id);
            this.id = -1;
        }
    }
}

And use it:

var myTimer = new CustomTimer(function () {
    console.log("It's been 5 seconds. Execute the function again.");
    this.start(5000);
});

myTimer.start(5000);

You can change the function to execute in any moment and also the interval. For example: run a start event in 1 second and then, 5 times every 5 seconds do other thing:

var secondFunc = function () {
    console.log("It's been 5 seconds. Execute the function again.");

    if (++this.count < 5)
        this.start(5000);
}

var myTimer = new CustomTimer(function () {
    console.log("First event at 1 second");

    this.count = 0;
    this.func = secondFunc;
    this.start(5000);
});

myTimer.start(1000);
about 4 years ago · Juan Pablo Isaza Report

0

The reason why the timer doesn't stop is because you only store the timeout ID of the initial setTimeout call. When repeatingFunc is called another setTimeout callback is registered, which has a new timeout ID.

So when you try to clear the timeout using clearTimeout(ref.current) you are passing an outdated timeout ID.

Since the previous setTimeout registration is already called, you no longer need to store the previous ID and you could simply replace it with the new one.

const ref = useRef(null);

useEffect(() => {
  if (!stream) {
    ref.current = setTimeout(repeatingFunc, 1000);
  } else {
    clearTimeout(ref.current);
  }
}, [stream]);

function repeatingFunc() {
  console.log("It's been 5 seconds. Execute the function again.");
  ref.current = setTimeout(repeatingFunc, 5000);
  // ^^^^^^^^^^^^ update timeout ID after registering a new timeout
}

Note that the scope of repeatingFunc is fixed in a point in time where you initially call setTimeout(repeatingFunc, 1000), meaning that things like states and properties can contain outdated values. This might or might not become a problem depending on your context.


You might also want to add a useEffect cleanup function that clears the timeout on component dismount. Otherwise your loop keeps going, even when the component is no longer mounted.

useEffect(() => {
  if (!stream) {
    ref.current = setTimeout(repeatingFunc, 1000);
  }

  return () => clearTimeout(ref.current);
}, [stream]);

Assuming stream is a boolean (true/false) the above will clear the current timeout whenever the value of stream is changed, or on component dismount.

When stream changes from true to false, the cleanup function will be called and the timeout will be cleared.

When stream changes from false to true the cleanup function is also called, but results in a no-op because the current ref.current is no longer registered. Then a new timeout is set.

Passing an invalid ID to clearTimeout() silently does nothing; no exception is thrown.

See: MDN - clearTimeout()

about 4 years ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!