Home > Mobile >  React setInterval in UseEffect not updating correctly in background tab
React setInterval in UseEffect not updating correctly in background tab

Time:01-23

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;

CodePudding user response:

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.

  •  Tags:  
  • Related