I am trying to code a (pomodoro) timer, and I keep running into this issue where if the code is running in a background tab for a period of time, about 5-10 minutes + (on chrome at least), it lags behind and ends up taking longer than it should to complete the timer. I have changed the interval to be 1000 ms, and tried to use refs instead of state.
Code is available on github
Demo at gh-pages
import { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { ReactDOM } from 'react-dom';
const Timer = ({ time, addTomato }) => {
const [ timeRemaining, setTimeRemaining ] = useState(time * 60);
const [ isPaused, setIsPaused ] = useState(true);
const timeRemainingRef = useRef(timeRemaining);
const isPausedRef = useRef(isPaused);
let navigate = useNavigate();
let minutes = Math.floor(timeRemaining / 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
});
let seconds = Math.floor(timeRemaining % 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
});
function timer() {
if (isPausedRef.current) return;
if (timeRemainingRef.current === 0) {
addTomato();
navigate("/trackodoro/break/");
return
}
timeRemainingRef.current --;
document.title = `Trackodoro ${Math.floor(timeRemainingRef.current / 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
})}:${Math.floor(timeRemainingRef.current % 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
})}`
setTimeRemaining(timeRemainingRef.current)
}
useEffect( //maybe i dont wanty to use useeffect
() => {
const myTimer = setInterval(timer, 1000);
return () => clearInterval(myTimer); //when I pause, it doesnt count down to next number
},
[]
);
const pauseTimer = () => {
isPausedRef.current = !isPausedRef.current
setIsPaused(isPausedRef.current);
};
const resetTimer = () => {
!isPausedRef.current && pauseTimer();
timeRemainingRef.current = time * 60
setTimeRemaining(timeRemainingRef.current);
}
return (
<div>
<h1>
{minutes}:{seconds}
</h1>
<button className="btn btn-block" onClick={pauseTimer}>
{isPaused && timeRemaining === time * 60 ? 'Start' : isPaused ? 'Resume' : 'Pause'}
</button>
<button className="btn btn-block" onClick={resetTimer}>
Reset
</button>
</div>
);
};
export default Timer;
instead of timeRemainingRef.current --
, you should probably want to use a end datetime.
Example (psuedo code) with date-fns.
const expiredate = addMinutes(new Date(), 5) //expires in 5 minutes.
At the place where you need timeRemainingRef.current --
timeRemainingRef.current = differenceInSeconds(expiredate, new Date())
timeRemainingRef.current
will be the number of seconds left to the expiredate
Using difference in datetime is probably more reliable than using a +1 counter.
Reference for addMinutes - https://date-fns.org/v2.28.0/docs/addMinutes
Reference for differenceInSeconds - https://date-fns.org/v2.28.0/docs/differenceInSeconds
date-fns is a lightweight datetime utility.