Reputation: 35
I am trying to prevent the counter from reloading on page refresh. Here is the code:
import React, { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import { timerEnd } from "./states-manager/timer-slice";
export default function Timer() {
// const lol =localStorage.getItem('timer')
const [countdown, setCountdown] = useState({
expirityTime: 100,
expired: false
});
const [timer, setTimer] = useState()
const [focus, setFocus] = useState(true)
const dispatch = useDispatch();
// Stop Counter when tab is switched
useEffect(()=>{
window.onblur = function () {
setFocus(false)
};
window.onfocus = function () {
setFocus(true)
setTimer(countdown.expirityTime)
};
})
useEffect(() => {
let time = countdown.expirityTime;
if (time > 0 && (focus)) {
const timerId = setTimeout(() => {
setCountdown({ expirityTime:time- 1 });
}, 1000);
return () => clearTimeout(timerId);
}
});
//Passing CountDown Vlaue to the Quiz.js through Redux Toolkit
dispatch(timerEnd(timer));
return (
<div className="App">
<div className="timer">
Time Remaining: {Math.floor(countdown.expirityTime / 60)}:
{countdown.expirityTime % 60}
</div>
</div>
);
}
I am able to store the countdown time in local storage but it also goes to restart due to the code. Let me know if you can help me with this. If you need any further information, please let me know.
Upvotes: 0
Views: 2874
Reputation: 517
I made a react hook which can be used as a timer or a count down timer at client side, please find the code and exmaple at github: PersistantTimer
No dependency required except react.
or you can find the code below:
import { useState, useEffect, useRef } from "react";
const initTimer = { //initial value of ref
lastSavedElapsedTime: 0,
elapsedTime: 0,
intervalId: null as ReturnType<typeof setInterval> | null,
start: 0,
manuallyPaused: false
}
interface OPTIONS {
updateFrequency?: number,
maximumValue?:number,
callback?:(()=>void)
LocalStorageItemName?:string
}
const defaultOptions:OPTIONS = {
updateFrequency: 1,
maximumValue:0,
callback:undefined,
LocalStorageItemName:'Persistant_timer'
}
const usePersistantTimer = (
pauseOnNoFocus: boolean = true, {
updateFrequency = 1,
maximumValue = 0,
callback,
LocalStorageItemName= 'Persistant_timer'}:OPTIONS = defaultOptions
): [number, () => void, () => void, () => void] => {
const timer = useRef(initTimer)
const cu = timer.current
const getValueFromLocalStorage = () => {
let v = parseInt(localStorage.getItem(LocalStorageItemName) || '0')
if (isNaN(v) || v < 0) v = 0
cu.lastSavedElapsedTime = v
cu.elapsedTime = 0
cu.start = new Date().getTime()
}
const [elapsedTime, setElapsedTime] = useState(cu.lastSavedElapsedTime)
const PIN = (i: number) => { // set parameter to default 1 if the paramenter is not legal number.
return (i > 1 && Number.isInteger(i)) ? i : 1
}
const updateFrequnce = PIN(updateFrequency)
const start = () => {
if (cu.manuallyPaused) cu.manuallyPaused = false
if (!cu.intervalId) {
getValueFromLocalStorage()
cu.intervalId = setInterval(() => {
cu.elapsedTime = new Date().getTime() - cu.start + cu.lastSavedElapsedTime
//t preserve real elapsed time.
if (!(cu.elapsedTime % updateFrequnce)) setElapsedTime(cu.elapsedTime)
if (maximumValue && cu.elapsedTime >= maximumValue * 1000) {
if (callback) callback()
cu.elapsedTime = 0
cu.manuallyPaused = true
pause()
}
localStorage.setItem(LocalStorageItemName, cu.elapsedTime.toString())
}, 1000)
}
}
const pause = () => {
cu.lastSavedElapsedTime = cu.elapsedTime
if (cu.intervalId) {
clearInterval(cu.intervalId)
cu.intervalId = null
}
}
const manuallyPause = () => {
cu.manuallyPaused = true
pause()
}
const resetTimer = () => {
cu.lastSavedElapsedTime = 0
cu.elapsedTime = 0
localStorage.setItem(LocalStorageItemName, "0")
cu.start = new Date().getTime()
setElapsedTime(0)
}
useEffect(() => {
getValueFromLocalStorage()
window.onblur = () => {
if (pauseOnNoFocus) pause()
}
window.onfocus = () => {
if (cu.manuallyPaused) return
if (pauseOnNoFocus) start()
}
start()
return () => {
if (cu.intervalId) {
clearInterval(cu.intervalId)
cu.intervalId = null
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pauseOnNoFocus, LocalStorageItemName, maximumValue, callback, updateFrequency])
return [elapsedTime, start, manuallyPause, resetTimer]
}
export default usePersistantTimer
Upvotes: 1
Reputation: 1331
I would actually re-think how the current timer is implemented, to make it more accurate.
But you can take advantage of the window before unload
event, and set a parallel timer that updates local storage at an interval. That said you need to be careful with setting to local storage
too frequently because it hits performance a good deal, if your saving a ton of objects/data, especially on smaller devices. This is because reading and writing to localstorage
is a synchronous operation, and usually requires JSON.stringify
and JSON.parse
to write and read to it. My implementation of a timer is below. The key takeaways are that you need to:
import React, { useState, useEffect } from "react";
import { unstable_batchedUpdates } from "react-dom";
import {useDispatch} from 'react-redux'
function addMinutes(date, minutes) {
return new Date(date.getTime() + minutes * 60000);
}
function addMillseconds(date, millseconds) {
return new Date(date.getTime() + millseconds);
}
export default function Timer() {
const currDate = new Date();
const [timeInactiveStart, setTimeInactiveStart] = useState(currDate);
const [inactiveTimerActive, setInactiveTimerActive] = useState(false);
const [timeStart, setTimeStart] = useState(currDate);
const [timeEnd, setTimeEnd] = useState(addMinutes(currDate, 10));
const timeRemaining = timeEnd.getTime() - timeStart.getTime();
const dispatch = useDispatch()
// Stop Counter when tab is switched
useEffect(() => {
window.onblur = function () {
//start parallel timer
const timeStartDate = new Date();
//we save here in case the user closes the page while away.
//that way we still know how much time they have left
const timerObj = {
startTime: timeStartDate,
endTime: timeEnd,
remainingTime: timeEnd.getTime() - timeStartDate.getTime(),
inactiveTimerActive: true,
};
localStorage.setItem("timerData".JSON.stringify(timerObj));
setTimeInactiveStart(timeStartDate);
//stop timer
setInactiveTimerActive(true);
};
window.onfocus = function () {
//end parallel timer
const timeInactiveEnd = new Date();
const timeElapsedInactive =
timeInactiveEnd.getTime() - timeInactiveStart.getTime();
//add time to intervals and now we can store them, so its not out of sync
const newEndTime = addMillseconds(timeEnd, timeElapsedInactive);
const newStartTime = addMillseconds(timeStart, timeElapsedInactive);
const timerObj = {
startTime: newStartTime.toString(),
endTime: newEndTime.toString(),
//we store this in case a user exists the page, we have a restarting point
remainingTime: newEndTime.getTime() - newStartTime.getTime(),
inactiveTimerActive: false,
};
unstable_batchedUpdates(() => {
localStorage.setItem("timerData", JSON.stringify(timerObj));
setTimeEnd(newEndTime);
setTimeStart(newStartTime);
//restart timer
setInactiveTimerActive(false);
});
};
window.onbeforeunload = function () {
//by nature this wont always occur, so
//if you need to keep the timer countdown with higher integrity,
// consider updating to local storage every minute or 5 minutes, depending on
// your use case. However, every second would be too frequent
const timerObj = {
startTime: timeStart.toString(),
endTime: timeEnd.toString(),
//we store this in case a user exists the page, we have a restarting point
remainingTime: timeEnd.getTime() - timeStart.getTime(),
inactiveTimerActive: inactiveTimerActive,
};
localStorage.setItem("timerData", JSON.stringify(timerObj));
};
});
//set a timer for a custom interval.
// To create checkpoints. However, the lower the time between intervals,
//the larger the performance hit, so don't update every second. In this example it updates every minute
useEffect(() => {
const updateStorage = setInterval(() => {
const timerObj = {
startTime: timeStart,
endTime: timeEnd,
inactiveTimerActive: inactiveTimerActive,
remainingTime: timeEnd.getTime() - timeStart.getTime(),
};
localStorage.setItem("timerData", JSON.stringify(timerObj));
}, 60000);
return () => clearInterval(updateStorage);
}, [timeEnd, timeStart, inactiveTimerActive]);
useEffect(() => {
const timer = setInterval(() => {
//we increment if these are correct
if (!inactiveTimerActive && timeStart.getTime() < timeEnd.getTime()) {
setTimeStart(new Date());
}
//clear local storage if timer has ended
else if (timeStart.getTime() > timeEnd.getTime())
localStorage.removeItem("timerData");
}, 1000);
return () => clearInterval(timer);
}, [inactiveTimerActive, timeStart, timeEnd]);
//on mount we fetch from localstorage our timer values
useEffect(() => {
const timerData = localStorage.getItem("timerData");
if (timerData) {
const data = JSON.parse(timerData);
const newDate = new Date();
unstable_batchedUpdates(() => {
setTimeStart(new Date());
//add time remaining since timer was inactive when tab was closed
setTimeEnd(addMillseconds(newDate, data.remainingTime));
});
}
}, []);
//here you can pass time remaining anywhere, etc.
//Passing CountDown Vlaue to the Quiz.js through Redux Toolkit
//dispatch(timerEnd(timer));
return (
<div className="App">
<div className="timer">
Time Remaining:{" "}
{timeRemaining > 0 ? Math.floor(timeRemaining / 1000) : 0} seconds
</div>
</div>
);
}
Upvotes: 1