1

I’m building with React a timer that multiple people will see at the same time. I’ve noticed that, once the timer has been opened by two persons on different tabs, if the user looking at the timer changes of tab and comes back, the timer gets out of sync (dragged a few seconds).

Inside the timer component I’m providing a duration prop and I have a secondsToCountdown internal state that gets updated every second with an interval inside the component. The interval is something like this (using hooks btw):

const [secondsToCountdown, setSecondsToCountdown] = useState(duration * 60);

const tick = () => {
    setSecondsToCountdown(secondsToCountdown - 1);  
};

useInterval(
  () => {
    tick();
  },
  running ? 1000 : null
);

I’m guessing that for some reason the interval stops or runs slowly when the component is out of view. Is there a workaround for this? Am I doing something wrong here? Is the only solution just to use something along the lines of the visibilitychange event?

oquiroz
  • 85
  • 5
  • 14
  • 1
    Intervals in inactive tabs are unreliable. You could [put the interval in a web worker](https://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs#answer-31105370), or instead of incrementing every tick you could store a start `Date` and compare against that every time the interval function is run. – Tholle Apr 04 '19 at 10:35
  • 1
    @Tholle oohh. Makes sense. I'll look into calculating the time left on the spot with some `Date` objects. Thanks! – oquiroz Apr 04 '19 at 10:45

1 Answers1

1

I think you can use requestAnimationFrame and instead counting down from a number you can compare the current datetime with the target datetime I made a sample sandbox here https://codesandbox.io/s/0v3xo8p4yp.

import React, { useState } from "react";
import ReactDOM from "react-dom";

const countDown = 20 * 60 * 1000; //20 minutes
const defaultTargetDate = new Date().getTime() + countDown;

const App = () => {
  const [targetDate, setTargetDate] = useState(new Date(defaultTargetDate));
  const [remainingSeconds, setRemainingSeconds] = useState(countDown / 1000);

  const countItDown = () =>
    requestAnimationFrame(() => {
      const diff = Math.floor((targetDate - new Date().getTime()) / 1000);
      setRemainingSeconds(diff);
      if (diff > 0) {
        countItDown();
      }
    });
  countItDown();
  return <div>{remainingSeconds} sec</div>;
};
duc mai
  • 1,412
  • 2
  • 10
  • 17