Search code examples
javascriptreactjsreact-hooksnext.jstimer

Getting always initial value of state after updating the state from the local storage


I'm using react-timer-hook package in my next.js project to display a timer like you can see in the screenshot below:

Timer Screenshot

Now the issue is I want to persist this timer elapsed time into the local storage and whenever the page reloads manually then I need the Timer to start from that specific elapsed time that I'm trying to get from the local storage, but whenever I reload the page manually then Timer starts from the initial state's value. below are the codes:

Timer Component

function Timer({ seconds, minutes, hours }) {
    return (
        <Typography variant="h5" fontWeight={'bold'} component={'div'}>
            <span>{String(hours).padStart(2, '0')}</span>:
            <span>{String(minutes).padStart(2, '0')}</span>:
            <span>{String(seconds).padStart(2, '0')}</span>
        </Typography>
    );
}

I'm adding 3600 seconds into expiryTimestamp i.e., current date and time to get one hour of Timer.

let expiryTimestamp = new Date();
expiryTimestamp.setSeconds(expiryTimestamp.getSeconds() + 3600);

Aslo I'm using another state with same 3600 seconds initial value

const [elapsed, setElapsed] = useState(3600);

I'm using useEffect and decrementing the elapsed value on every second into local storage.

useEffect(() => {
    const interval = setInterval(() => {
        localStorage.setItem('elapsed', JSON.stringify(elapsed--));
    }, 1000);
    return () => clearInterval(interval);
}, [elapsed]);

Now I'm getting the elapsed value from the local storage

useEffect(() => {
    const elapsed = localStorage.getItem('elapsed');
    if (elapsed) {
        setElapsed(JSON.parse(elapsed));
    }
}, []);

Again I'm using another variable to create current date and time + elapsed value

let currentTime = new Date();
currentTime.setSeconds(currentTime.getSeconds() + elapsed);

Finally I'm passing the currentTime in useTimer hook

const { seconds, minutes, hours } = useTimer({
    expiryTimestamp: currentTime,
    onExpire: handleForm,
});

Elapsed time is properly storing in the local storage, but still Timer starts from 3600 seconds.


Solution

  • We can use expiryTimestamp value of use timer as function to initiate its value. Check the following component

    import { useEffect } from 'react';
    import { useTimer } from 'react-timer-hook';
    
    export default function Timer() {
      const { seconds, minutes, hours } = useTimer({
        expiryTimestamp: () => {
          /** determine the expiry time stamp value all at once */
          const time = new Date(),
            elapsedTime = Number(window.localStorage.getItem('elapsed')) || 3600;
          time.setSeconds(time.getSeconds() + elapsedTime);
          return time;
        },
        onExpire: () => alert('expired')
      });
    
      /** update elapsed value in local storage */
      useEffect(() => {
        const elapsedSeconds = hours * 60 * 60 + minutes * 60 + seconds;
        window.localStorage.setItem('elapsed', elapsedSeconds);
      }, [seconds]);
    
      return (
        <div>
          <span>{String(hours).padStart(2, '0')}</span>:
          <span>{String(minutes).padStart(2, '0')}</span>:
          <span>{String(seconds).padStart(2, '0')}</span>
        </div>
      );
    }
    
    

    If you're using this component in next.js. You should use dynamic import with ssr disabled. otherwise you'll receive an error because SSR doesn't recognize the window.localStorage api. Check below

    
    import dynamic from 'next/dynamic';
    import React from 'react';
    
    const Timer = dynamic(() => import('./timer'), { ssr: false });
    
    export default function Index = () => {
      return <Timer />;
    };