Reputation: 1157
I'm using react-timer-hook
package in my next.js project to display a timer like you can see in the screenshot below:
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.
Upvotes: 1
Views: 510
Reputation: 1471
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 />;
};
Upvotes: 4
Reputation: 1188
It looks like your first useEffect hook is overwriting the value in local storage before you set your state from what's stored there.
Hooks fire in the order in which they are defined within a component, so your component is doing:
You could try checking the local storage value and starting your interval in the same hook.
I'd first start by starting my default state with undefined
const [elapsed, setElapsed] = useState();
This lets me control the starting of the timer entirely within the useEffect
useEffect(() => {
// make sure your first action is to get the elapsed time in local storage
const storedElapsed = localStorage.getItem('elapsed');
// work out if this is the initial load or not
if (!elapsed) {
// component state is not yet set, so use either local storage or the default
const newElapsedState = JSON.parse(storedElapsed) || 3600;
setElapsed(newElapsedState);
} else {
// we now know that state is set correctly, so can ignore local storage and start the timer
const interval = setInterval(() => {
localStorage.setItem('elapsed', JSON.stringify(elapsed--));
}, 1000);
return () => clearInterval(interval);
}
}, [elapsed]);
You will need to handle your first render being undefined
, but after that your flow should be consistent as you have control over what is being set in what order.
You can control this by the order in which you define your useEffects, but I personally find this way a bit clearer as the logic is grouped together.
Upvotes: 0