Reputation: 36
I'm trying to create a timer with React Hook to use it on a Context. I have no problem when running it using Context and Vanilla JS, but when I use Hooks it seems to be a few miliseconds behind.
My useTimerHook:
import { useState } from 'react';
const useTimerHook = () => {
const [minutes, setMinutes] = useState(0);
const [seconds, setSeconds] = useState(0);
const [miliSeconds, setMiliSeconds] = useState(0);
function write() {
let mt, st, mlst;
setMiliSeconds(miliSeconds + 1);
if (miliSeconds > 99) {
setSeconds(seconds + 1);
setMiliSeconds(0);
}
if (seconds > 59) {
setMinutes(minutes + 1);
setSeconds(0);
}
mlst = ('0' + miliSeconds).slice(-2);
st = ('0' + seconds).slice(-2);
mt = ('0' + minutes).slice(-2);
return `${mt}:${st}:${mlst}`;
}
function reset() {
setMinutes(0);
setMiliSeconds(0);
setSeconds(0);
}
return {
write,
reset,
};
};
My Context:
import useTimerHook from '@hooks/useTimerHook';
const FightContext = createContext();
function FightProvider(props) {
const [time, setTime] = useState('Presiona Start');
const [status, setStatus] = useState(false);
const { write: write, reset: reset } = useTimerHook();
const startFight = () => {
setStatus(true);
};
const endFight = () => {
setStatus(false);
reset();
};
useEffect(() => {
if (status) {
const timer = setTimeout(() => setTime(write()), 10);
return () => {
clearTimeout(timer);
};
}
});
return (
<FightContext.Provider
value={{
time,
startFight,
endFight,
status,
}}
>
{props.children}
</FightContext.Provider>
);
}
export { FightContext, FightProvider };
As I said, when I try to run this the timer is slower than it should be. I.e, when my phone timer is at 15 seconds, the Hook timer is at 13.
I'm pretty sure that I missing something obvious here, or maybe I don't fully understand how Hooks and Context works.
Upvotes: 1
Views: 659
Reputation: 3892
Instead of keeping the "timer value" in the state, why not simply rely on the device time and apply any offset rules. I suggest keeping the start and stop timestamps in the state and then simply calculate the difference. Think of it like marking points on measuring tape and then calculating the difference. That way, issues such as frame times won't affect your timer value.
To display the clock "running" you can use setInterval()
, e.g. every second or even faster and then display the difference between the start timestamp and timestamp for now()
.
To have an ability to pause and resume the timer, I would keep and array of objects of the form:
// example:
// [
// {value: 0, type: "start"},
// {value: 1000, type: "stop"},
// {value: 5000, type: "start"},
// ]
const [timeStamps, setTimestamps] = useState([]);
To add a new timestamp to state:
const nextStampType
= timeStamps.length > 0
? timeStamps[timeStamps.length - 1].type === "start" ? "stop" : "start"
: "start";
setTimeStamps([...timestamps, {value: Date.now(), type: nextStampType}
Then you can use a utility function that loops over the array to calculate the timer value. Depending on your use case, you may either share the timer value or the timeStamps
array via React Context.
Upvotes: 2