0

The title pretty much says it all, but I'll include my somewhat complicated React timer code anyway.

My app is basically an alarm app, and it keeps perfect time most (about 3/4s) of the time. But sometimes when it is in a background tab, the timing is off (15 minutes could take 20 minutes, for example).

So I'm wondering if this is a common problem that anyone knows about. Does my browser send the clock to sleep to save energy perhaps?

I see other browser based alarm clocks on the web, but I haven't been able to find much about this problem.

Thanks,

class Clock extends React.Component {
  
  render() {
    return (
      <>
        <div className="floatLeft">
          <div id="timer">
            <Countdown updateDB={this.props.updateDB} taskBarOpen={this.props.taskBarOpen} pauseForModal={this.props.pauseForModal} cycle={this.props.cycle} taskBarCounter={this.props.taskBarCounter}>
            </Countdown>
          </div>
        </div>
      </>
    );
  }

}


function Countdown(props) {

  const [timer, setTimer] = React.useState({
    name: 'timer',
    onBreak: false,
    firstStart: true,
    isPaused: true,
    pauseForModal: false,
    time: 0,
    timeRemaining: 0,
    timerHandler: null,
    cycle: 0,
    timeEqualsTimeRemaning: true,
    showToolbar: false

  })

  const [taskBarCounter] = React.useState({
    counter: 0
  })

  let taskBarProps = props.taskBarCounter
  let allowCountdownRestart = (taskBarCounter.counter !== taskBarProps) ? true : false
  const context = React.useContext(ApiContext);
  let breakDurations = context.prefs

  React.useEffect(() => {
    if (allowCountdownRestart) {
      taskBarCounter.counter = taskBarCounter.counter + 1
      allowCountdownRestart = false
    } else {
      allowCountdownRestart = true
    }
  }, [props, allowCountdownRestart])

  React.useEffect(() => {
    if (props.pauseForModal) {
      timer.pauseForModal = true
      // handlePause()
    } else {
      setTimeout(() => {
        timer.pauseForModal = false
        // handleStart()
      }, 300);
    }
  }, [props.pauseForModal])

  React.useEffect(() => {
    if (allowCountdownRestart) {
      if (!timer.pauseForModal) {
        setTimer((timer) => ({
          ...timer,
          time: props.cycle * 60,
          timeRemaining: props.cycle * 60,
          cycle: props.cycle,
          onBreak: false
        }));
      }
      if (timer.isPaused) {
        // timer.isPaused = false;
      }
    }

  }, [props])

  React.useEffect(() => {
    if (timer.time === 0 && !timer.firstStart) {
      setTimeout(function () {
        if (timer.onBreak) {
          playBark()
          timer.showToolbar = false
          timer.onBreak = false
        } else {
          const breakDuration = breakDurations[timer.cycle] * 60

          playTweet()
          if (breakDuration !== 0) {

            // playTweet()
            setTimer((timer) => ({
              ...timer,
              onBreak: true,
              time: breakDuration,
              timeRemaining: breakDuration
            }));
          } else {
            // playBark()
            timer.showToolbar = false
          }
          props.updateDB(timer.cycle)
        }
      }, 1000);

    } else {

      if (timer.time === timer.timeRemaining) {
        timer.firstStart = false
        timer.showToolbar = true
        handleStart()
      }

    }

  }, [timer.time, timer.time === timer.timeRemaining])


  React.useEffect(() => {

    if (timer.timeRemaining === 0) {
      clearInterval(timer.timerHandler)
      setTimer((timer) => ({
        ...timer,
        time: 0,
        isPaused: true
      }));

    }

  }, [timer.timeRemaining])

  const updateTimeRemaining = e => {
    setTimer(prev => {
      return { ...prev, timeRemaining: prev.timeRemaining - 1 }
    })
  }
  const handleStart = e => {
    if (timer.time !== 0) {
      clearInterval(timer.timerHandler)
      const handle = setInterval(updateTimeRemaining, 1000);
      setTimer({ ...timer, isPaused: false, timerHandler: handle })
    }
  }
  const handlePause = e => {
    clearInterval(timer.timerHandler)
    setTimer({ ...timer, isPaused: true })
  }

  const timeFormat = (duration) => {

    if (duration > 0) {
      var hrs = ~~(duration / 3600);
      var mins = ~~((duration % 3600) / 60);
      var secs = ~~duration % 60;
      var ret = "";
      if (hrs > 0) {
        ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
      }
      ret += "" + mins + ":" + (secs < 10 ? "0" : "");
      ret += "" + secs;
      return ret;
    } else {
      return "00:00"
    }
  }

  const handleSkip = () => {
    clearInterval(timer.timerHandler)
    setTimer({ ...timer, timeRemaining: 0 })
  }

  const handleStop = () => {
    clearInterval(timer.timerHandler)
    setTimer({ ...timer, onBreak: true, cycle: 0, timeRemaining: 0 })

  }

  const [playBark] = useSound(bark,
    { volume: 0.35 }
  );

  const [playTweet] = useSound(tweet,
    { volume: 0.20 }
  );

  const [playGong] = useSound(gong,
    { volume: 0.20 }
  );


  return <React.Fragment>

    {timer.onBreak ?
      <div><h2 className="display-timer-header">On Break </h2> <h2 className="display-timer">{timeFormat(timer.timeRemaining)}</h2></div>
      : <div><h3 className="display-timer-header"> Time Left </h3> <h3 ref={context.timerRef} className="display-timer">{timeFormat(timer.timeRemaining)}</h3></div>} 

   
      <div className="toolbar-container">
        <div className={`toolbar-icons ${props.taskBarOpen ? "taskbar-open" : ""}`}>
          <i className="tooltip"><Stop className="toolbar-icon" onClick={handleStop}></Stop>
            <span className="tooltiptext">Stop</span></i>
            {!timer.isPaused ?
            <i className="tooltip pause"><PausePresentation className="toolbar-icon" onClick={handlePause}></PausePresentation>
              <span className="tooltiptext pause-tooltip">Pause</span></i>
            :
            <i className="tooltip pause"><PlayCircleOutline className="toolbar-icon" onClick={handleStart}></PlayCircleOutline>
              <span className="tooltiptext">Start</span></i>
          }
          <i className="tooltip"><SkipNext className="toolbar-icon" onClick={handleSkip} ></SkipNext>
            <span className="tooltiptext">Skip to Break</span></i>
         
        </div>
      </div>

  </React.Fragment>
}




export default Clock;
Louis Sankey
  • 481
  • 8
  • 26
  • Googling around it seems that inactive/background tabs get reduced resources assigned to them, which can lead to stuff like this. It seems that you'd have to use a web worker to get around this, as these are their own processes and don't suffer from throttling. See the answers here: https://stackoverflow.com/questions/5927284/how-can-i-make-setinterval-also-work-when-a-tab-is-inactive-in-chrome – Jayce444 Mar 11 '21 at 23:31
  • 1
    @Jayce444 - thanks! That's exactly what I was looking for. – Louis Sankey Mar 11 '21 at 23:44

0 Answers0