Why setInterval does not work correactly? - javascript

I am trying to make a stopwatch (00:00:00:00).But my one sec is slower than real one sec.
I also changed the value of setInterval 10 to 1 but nothing changed. When I changed it as 100, it worked, time flowed slower.
(00:00:00:00)=(hh:mm:ss:ms)
Here is a part of my code:
const [time, setTime] = useState({
ms: 0,
ss: 0,
mm: 0,
hh: 0
})
let degisenMs = time.ms,
degisenH = time.hh,
degisenM = time.mm,
degisenS = time.ss;
const run = () => {
if (updatedMs === 100) {
updatedS++;
updatedMs = 0
}
if (updatedS === 60) {
updatedM++;
updatedS = 0;
}
if (M === 60) {
updatedH++;
updatedM = 0
}
updatedMs++;
return (setTime({
ms: updatedMs,
ss: updatedS,
mm: updatedM,
hh: updatedH
}))
}
const start = () => {
setStatus(1)
run()
setInterv(setInterval(run, 10))
}

The problem is that setInterval is not exact, it is approximate. One option is to use web workers to increase accuracy as described in the link, but it is still not exact.
When it comes to measuring time, it is better to track the start timestamp and figure out how much time has passed at each tick/update. You can then update the UI or trigger an alarm etc. Here is some pseudocode.
const [ startTime, setStartTime ] = useState(null)
const [ intervalId, setIntervalId ] = useState(null)
function tick() {
const now = new Date()
const elapsedMs = now - startTime
// Update UI etc using elapsedMs
}
function start() {
setStartTime(new Date())
// Run tick() every 100ms
setIntervalId(setInterval(tick, 100))
}
function stop() {
clearInterval(intervalId)
}

Related

Checking setInterval every 5 minutes on the 0 second

const secondsInterval = () => {
const date = getNow();
if (dayjs(date).minute() % 5 !== 0 && dayjs(date).second() !== 0) {
console.log("return...");
return;
}
console.log("checking...");
...
};
// Check every second, if we're at the 5-minute interval check.
setInterval(secondsInterval, 1000);
This seems to get stuck. It's "checking" on every second of each 5 minute mark. What am I doing wrong? Thanks in advance.
Goal: To "check" every minute and 00 seconds: :00:00, :05:00, :10:00, , :15:00, etc Thanks again.
You should find out what's the time to your next rounded 5 min. like this:
const FIVE_MIN = 1000 * 60 * 5;
function waitAndDoSomething() {
const msToNextRounded5Min = FIVE_MIN - (Date.now() % FIVE_MIN);
console.log(`Waiting ${msToNextRounded5Min}ms. to next rounded 5Min.`);
setTimeout(() => {
console.log('It is now rounded 5 min');
waitAndDoSomething();
}, msToNextRounded5Min);
}
waitAndDoSomething();
If all you care about is executing some code every 5 mins, then don't have it execute every second needlessly, only to return out. Just have it run every 5 mins (300000 MS) and have it do what you need to do and remove all the checking for 5 minute mark code out its unnecessary.
const secondsInterval = () => {
// -------------------- Remove ----------------
//const date = getNow();
//if (dayjs(date).minute() % 5 !== 0 && dayjs(date).second() !== 0) {
// console.log("return...");
// return;
//}
// -------------------- Remove ----------------
console.log("checking...");
...
};
// Check every 5 mins
setInterval(secondsInterval, 300000);
Your logic for the if is screwy. Here I reversed it so the if takes care of "do the thing" and the else returns.
const secondsInterval = () => {
const date = dayjs(new Date());
if (dayjs(date).minute() % 5 == 0 && dayjs(date).second() == 0) {
console.log("checking...");
} else {
console.log("returning...");
return;
}
//...
};
// Check every second, if we're at the 5-minute interval check.
setInterval(secondsInterval, 1000);
<script src="https://unpkg.com/dayjs#1.8.21/dayjs.min.js"></script>

Timer slows down when performing other tasks in React

This might be a very common or easy issue to solve, but I'm very new to React (started today) and am not aware of this.
I'm trying to build a simple clicks per second application. Basically, an application that counts how many clicks you do per second. So, when the person starts clicking on the button a timer starts, and at the end, I divide clicks/seconds. The problem is that the timer slows down when I start clicking faster.
Here is a simplified version of my code:
function Clicker() {
const [clicks, setClicks] = useState(0);
const [milliSeconds, setMilliSeconds] = useState(0);
const [storeMilliSeconds, setStoreMilliSeconds] = useState(0);
const [seconds, setSeconds] = useState(0);
const [startTimer, setStartTimer] = useState(false);
const clickerHandler = () => {
setEffect(true);
setStartTimer(true);
setClicks(clicks + 1);
};
useEffect(() => {
if (seconds === 10 && milliSeconds === 0) {
setStartTimer(false);
}
if (startTimer === true) {
const interval = setInterval(() => {
setMilliSeconds((milliSeconds) => milliSeconds + 1);
setStoreMilliSeconds((storeMilliSeconds) => storeMilliSeconds + 1);
if (milliSeconds === 9) {
setSeconds((seconds) => seconds + 1);
setMilliSeconds(0);
}
}, 100);
return () => clearInterval(interval);
}
return console.log(
"nothing happening"
);
});
return (
<button
onClick={clickerHandler}
>
There's only one way to find out...
</button>
I'm very grateful for the help I can get, thanks!

Is there a way to iterate through a timer in React?

I'm trying to create a Pomodoro timer using Hooks and I have set up the basic functionality using useState and useEffect. I have a 25-minute timer that counts down and every time it gets to 0, it starts a break timer of 5 minutes. What I'm trying to figure out now is how to create an iteration that says "every 4 times the timer hits 0, change the break time from 5 minutes to 15 minutes and then, go back to 5 minutes." I thought of creating sessions that way it will say 4th session and then it will go back to 1. but I'm really not sure what to do here.
import React, { useState, useEffect } from "react";
function Pomodoro() {
const [minutes, setMinutes] = useState(25);
const [seconds, setSeconds] = useState(0);
const [displayMessage, setDisplayMessage] = useState(false);
const [session, setSession] = useState(1);
useEffect(() => {
let interval = setInterval(() => {
clearInterval(interval);
if (seconds === 0 && minutes !== 0) {
setSeconds(59);
setMinutes(minutes -1);
} else if (seconds === 0 && minutes === 0) {
let minutes = displayMessage ? 24 : 4;
let seconds = 59;
setSeconds(seconds);
setMinutes(minutes);
setDisplayMessage(!displayMessage);
} else {
setSeconds(seconds -1);
}
}, 1000);
}, [seconds]);
const timerMinutes = minutes < 10 ? `0${minutes}` : minutes;
const timerSeconds = seconds < 10 ? `0${seconds}` : seconds;
return (
<div className="pomodoro">
<div>Session:{session} </div>
<div className="message">
{displayMessage && <div>Break time! New Session starts in:</div>}
</div>
<div className="timer">
{timerMinutes}:{timerSeconds}
</div>
</div>
);
}
export default Pomodoro;
Your approach using a counter to keep track of the completed sessions seems to make sense. If you want to use a different amount of break time for every fourth iteration, you could use the remainder operator as below:
let breakTime = (session % 4) === 0 ? 14 : 0;
Then, you just need to make sure you are incrementing your session variable by one each time you complete a session. This also means you only want to increase it when you are not "on break" so you must make sure to guard against that.
Updating the answer with the full code that I tested to be working. Note the following changes I made:
I am only keeping track of the timer in seconds - this reduces the complexity inside useEffect and you can convert from seconds to other formats (try using the remainder operator again)
Moved the period lengths to constants
Renamed the variable displayMessage to isOnBreak for clarity
import React, { useState, useEffect } from "react";
// Define the period lengths (in seconds)
const workTime = 2;
const shortBreakTime = 4;
const longBreakTime = 6;
function Pomodoro() {
const [seconds, setSeconds] = useState(workTime);
// Renamed this variable for clarity to indicate it is a boolean
const [isOnBreak, setIsOnBreak] = useState(false);
const [session, setSession] = useState(1);
useEffect(() => {
let interval = setInterval(() => {
clearInterval(interval);
if (seconds === 0) {
let breakTime = (session % 4 === 0) ? longBreakTime : shortBreakTime;
let seconds = !isOnBreak ? breakTime : workTime;
// A session is complete when work and break is done,
// so only increment when finishing a break
if (isOnBreak) setSession(session+1);
setSeconds(seconds);
setIsOnBreak(!isOnBreak);
} else {
setSeconds(seconds -1);
}
}, 1000);
}, [seconds]);
// Here you could convert from seconds to minutes and seconds or whatever time format you prefer
const timerSeconds = seconds < 10 ? `0${seconds}` : seconds;
return (
<div className="pomodoro">
<div>Session:{session} </div>
<div className="message">
{isOnBreak && <div>Break time! New Session starts in:</div>}
</div>
<div className="timer">
{timerSeconds}
</div>
</div>
);
}

Building a React clock with hooks

I'm working on building a clock that counts up, just a practice exercise (I know there are better ways of doing this).
My issue is when a minute is added, the "addMinute" seems to run twice, and I can't for the life of me figure out how or why.
Here is a demo on codesandbox: https://codesandbox.io/s/restless-frost-bud7p
And here is the code at a glance:
(please note, the counter only counts up to 3 and counts faster; this is just for testing purposes)
const Clock = (props) => {
const [seconds, setSeconds] = useState(0)
const [minutes, setMinutes] = useState(0)
const [hours, setHours] = useState(0)
const addHour = () => {
setHours(p => p + 1)
}
const addMinute = () => {
setMinutes(prev => {
if (prev === 3) {
addHour()
return 0
} else {
return prev + 1
}
})
}
const addSecond = () => {
setSeconds(prevState => {
if (prevState === 3) {
addMinute()
return 0
} else {
return prevState + 1
}
})
}
useEffect(() => {
const timer = setInterval(addSecond, 600)
return () => clearInterval(timer)
}, [])
return (
<div>
<h1>time!</h1>
<p>
{hours < 10 ? 0 : ""}{hours}
:
{minutes < 10 ? 0 : ""}{minutes}
:
{seconds < 10 ? 0 : ""}{seconds}
</p>
<p>
{seconds.toString()}
</p>
</div>
)
}
The issue is that you are using the React.StrictMode wrapper in the index.js file.
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
So you should decide between using strict mode or having side effects, the easy way is just removing the React.StrictMode wrapper. The other way is removing side effects, where you only need to do the following:
Update your addSecond and addMinute functions to something like:
const addMinute = () => {
setMinutes((prev) => prev + 1);
};
const addSecond = () => {
setSeconds((prevState) => prevState + 1);
};
And your useEffect call to something like:
useEffect(() => {
if(seconds === 3) {
addMinute();
setSeconds(0);
};
if(minutes === 3) {
addHour();
setMinutes(0);
}
const timer = setInterval(addSecond, 600);
return () => clearInterval(timer);
}, [seconds, minutes]);
Here an updated version of your code: https://codesandbox.io/s/goofy-lake-1i9xf
A couple of issues,
first you need to use prev for minutes, so
const addMinute = () => {
setMinutes(prev => {
if (prev === 3) {
addHour()
return 0
} else {
return prev + 1
}
})
}
And then you need to remove the React.StrictMode wrapper component from index, which is what is actually causing the double increase, as part of what the strict mode does is
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
see: https://codesandbox.io/s/pensive-wildflower-pujmk
So I had no idea about strict mode and the intentional double renders. After reading the documentation I finally understand the purpose of this.
As such, it appears the best solution is to have no side effects from the useEffect, and instead, handle that logic outside of the effect, but still changing every second.
So, I set an effect that has a piece of state starting at zero and going up by one per second.
Then, with each change of "time", the useMemo will recalculate how many hours, mins and seconds the total time is.
The only thing I don't like is all those calculations running every render! (But realistically those take but a few miliseconds, so performance doesn't seem to be an issue).
const [time, setTime] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setTime(p => p + 1)
}, 999);
return () => clearTimeout(timer);
}, [userState]);
const timeJSON = useMemo(() => {
const hrs = Math.floor(time/3600)
const mins = Math.floor( (time-(3600*hrs)) / 60 )
const secs = time - (3600 * hrs) - (60*mins)
return {
hrs,
mins,
secs,
}
}, [time])
return (
<div>
<p>
{timeJSON.hrs < 10 ? 0 : ""}{timeJSON.hrs}
:
{timeJSON.mins < 10 ? 0 : ""}{timeJSON.mins}
:
{timeJSON.secs < 10 ? 0 : ""}{timeJSON.secs}
</p>
</div>
)
Thanks again for everyone pointing me in the right direction on this!

setTimeout calling the function prematurely

My library has test cases based on real time, and I noticed that tests will randomly fail with 1 millisecond error:
expect(received).toBeGreaterThanOrEqual(expected)
Expected: >= 1000
Received: 999
This seems to be due to setTimeout calling the function prematurely.
So I wrote a separate test script:
let last = Date.now()
setTimeout(next, 1000)
function next() {
if (Date.now() - last < 1000) process.exit(1)
last = Date.now()
setTimeout(next, 1000)
}
On Node.js v12.19.0, v14.15.3, v15.4.0, it will fail randomly: sometimes the script can continue to run, sometimes the script will exit soon.
This is not only happening on my local computer, but also on Github's CI server.
My questions: Is this a bug? Or some kind of expected behavior of setTimeout? Or Date.now() - time always needs to add 1 millisecond?
UPDATE: See also https://github.com/nodejs/node/issues/26578
Update: using git-bisect here is the culprit:
2c409a285359faae58227da283a4c7e5cd9a2f0c is the first bad commit
commit 2c409a285359faae58227da283a4c7e5cd9a2f0c
Date: Tue Aug 25 13:36:37 2020 -0600
perf_hooks: add idleTime and event loop util
Use uv_metrics_idle_time() to return a high resolution millisecond timer
of the amount of time the event loop has been idle since it was
initialized.
Include performance.eventLoopUtilization() API to handle the math of
calculating the idle and active times. This has been added to prevent
accidental miscalculations of the event loop utilization. Such as not
taking into consideration offsetting nodeTiming.loopStart or timing
differences when being called from a Worker thread.
PR-URL: https://github.com/nodejs/node/pull/34938
This seems like a bug, not an expected behavior. I would vote against always adding 1ms since the behavior is inconsistent. (However, will it ever be earlier more than 1 ms? I didn't observe more than 1ms) You may workaround the problem with the following:
const origSetTimeout = setTimeout;
setTimeout = (f, ms, ...args) => {
let o;
const when = Date.now() + ms,
check = ()=> {
let t = when - Date.now();
if (t > 0) Object.assign(o, origSetTimeout(check, t));
else f(...args);
};
return o = origSetTimeout(check, ms);
};
It will allow to clearTimeout() even while working around the problem.
Here is a browser code that simulates the problem and alternates the workaround every 3 seconds:
// Simulate the problem
const realOrigSetTimeout = setTimeout;
setTimeout = (func, ms, ...args) => realOrigSetTimeout(func, ms - Math.random(), ...args);
const ms = 200;
let when = Date.now() + ms;
setTimeout(next, ms);
function next() {
let now = Date.now();
setTimeout(next, ms);
console.log(now < when ? 'premature' : 'ok');
when = now + ms;
}
function workAround() {
console.log('Applying workaround');
const origSetTimeout = setTimeout;
setTimeout = (f, ms, ...args) => {
let o;
const when = Date.now() + ms,
check = ()=> {
let t = when - Date.now();
if (t > 0) Object.assign(o, origSetTimeout(check, t));
else f(...args);
};
return o = origSetTimeout(check, ms);
};
setTimeout(_=>{
console.log('Removing workaround');
setTimeout = origSetTimeout;
setTimeout(workAround, 3000);
}, 3000);
}
setTimeout(workAround, 3000);
Below is a nodejs code that will clearly show the problem ('p' among dots) and will apply the workaround after pressing enter.
'use strict';
const ms = 1;
let when = Date.now() + ms;
setTimeout(next, ms);
function next() {
let now = Date.now();
setTimeout(next, ms);
process.stdout.write(now < when ? 'p' : '.');
when = now + ms;
}
process.stdin.on('readable', _=> {
console.log('enabling workaround');
const origSetTimeout = setTimeout;
setTimeout = (f, ms, ...args) => {
let o;
const when = Date.now() + ms,
check = ()=> {
let t = when - Date.now();
if (t > 0) Object.assign(o, origSetTimeout(check, t));
else f(...args);
};
return o = origSetTimeout(check, ms);
};
});

Categories