Dear all professionals from everywhere.
I have a question from a beginner. I did not find any direct answers only some references...
I wrote a simple version of stopwatch (React) and when I try to test it on different browsers (Chrome, Mozilla and Edge) I have different speed results between Chrome(Edge) and Mozilla on this stopwatch. The difference is about 4 times.
I would be very grateful if you also send me a link with some theory (maybe).
Anyway thank you
P.S If you see something that seems strange in this code please tell me. That would be very useful for me.
import React, { useState } from "react";
import { useEffect } from "react";
import "./Stopwatch.css";
import Button from "./Button";
import Round from "./Round";
function Stopwach() {
const [timer, setTimer] = useState(0);
const [start, setStart] = useState(false);
const [round, setRound] = useState([]);
const [globeTimer, setGlobeTimer] = useState(0);
useEffect(() => {
let secondsCircle = document.querySelector(".svg01");
let milCircle = document.querySelector(".svg02");
let degreeS = 0;
let degreeM = 0;
degreeS = timer / 166.6666666666667;
secondsCircle.style.transform = `rotate(${degreeS}deg)`;
degreeM = timer / 3.6;
milCircle.style.transform = `rotate(${degreeM}deg)`;
}, [timer]);
function Circle() {
if (round.length < 10) {
if (round.length === 0) {
round[round.length] = timer;
setRound(round);
} else {
round[round.length] = timer - globeTimer;
setRound(round);
}
} else {
let firstElement = Math.min.apply(Math, round);
round.length = 0;
round[0] = firstElement;
round[round.length] = timer - globeTimer;
setRound(round);
}
setGlobeTimer(timer);
}
useEffect(() => {
let interval;
if (start) {
interval = setInterval(() => {
setTimer((timer) => timer + 4);
}, 1);
} else {
clearInterval(interval);
}
return () => {
clearInterval(interval);
};
}, [start]);
function go() {
setStart(!start);
}
function clear() {
setTimer(0);
setRound([]);
setGlobeTimer([])
}
return (
<div className="main_monitor">
<div className="svg">
<div className="svg01"></div>
<div className="svg02"></div>
</div>
<div className="main_monitor__timer">
<div className="mill">
{Math.trunc(timer / 1000 / 60) < 10 ? `0${Math.trunc(timer / 1000 / 60)}` : Math.trunc(timer / 1000 / 60)}
</div>
<div className="point">:</div>
<div className="mill">
{Math.trunc(timer / 1000) % 60 < 10 ? `0${Math.trunc(timer / 1000) % 60}` : Math.trunc(timer / 1000) % 60}
</div>
<div className="point">.</div>
<div className="mill">{timer % 1000 < 10 ? `00${timer % 1000}` : timer % 1000 < 100 ? `0${timer % 1000}` : timer % 1000} </div>
</div>
<div className="main_monitor__btns">
<Button go={go} btn={"play"} />
<Button go={go} btn={"stop"} />
<Button go={clear} btn={"recycle"} />
<Button go={Circle} btn={"history"} />
</div>
<Round rounds={round} />
</div>
);
}
export default Stopwach;
So, after 5 days and hole bunch of coffee...
According to Wiki - Firefox, Google and Edge is browsers with different engines.
I think, at least, it`s the root of this mistake.
I solved this with Date() constructor. Every time when we call setInterval - we will ask new "a single moment in time in a platform-independent format" (unfortunatly this part of definition was not translated (en - ru) on MDN). And based on this "single moment" we can calculate how much time passed from our time point...
Related
Trying to update and read state in React to switch between two different timers. I'm still new to programming and can't figure out why my component that displays the state "Session" or "Break" updates, but my conditional switchTimers() fails to switch timers based on that state.
const [timers, setTimers] = useState({
sessionTime: 25,
breakTime: 5,
});
const [timerDisplay, setTimerDisplay] = useState("Session");
const [timerActive, setTimerActive] = useState(false)
const [displayCount, setDisplayCount] = useState(1500);
const round = useRef();
function startStop(action, secondsLeft) {
const interval = 1000;
let expected = Date.now() + interval;
if (action === "start") {
setTimerActive(true)
round.current = setTimeout(step, interval);
function step() {
if (secondsLeft > 0) {
const drift = Date.now() - expected;
setDisplayCount((prevValue) => prevValue - 1);
secondsLeft --
expected += interval;
round.current = setTimeout(step, Math.max(0, interval - drift));
} else {
clearTimeout(round.current)
switchTimers()
}
}
} else if (action === "stop") {
clearTimeout(round.current);
setTimerActive(false);
}
}
function switchTimers() {
beep.current.play()
if (timerDisplay === "Session") {
setTimerDisplay("Break");
setDisplayCount(timers.breakTime * 60);
startStop("start", timers.breakTime * 60)
} else if (timerDisplay === "Break") {
setTimerDisplay("Session");
setDisplayCount(timers.sessionTime * 60);
startStop("start", timers.sessionTime * 60)
}
}
When the "Session" timer ends, it shows "Break" in my label that prints timerDisplay, but once "Break" timer ends, it reruns "Break" instead of switching to "Session". Any insight into whats going wrong?
Try writing you entire switchTimers function as a callback to the useEffect hook and add timerDisplay as a dependency. Also, please show the JSX you return from the component i.e. the entire code so that I can help you better.
I am new to javascript, and I made a counter. It works, however when the tab is inactive it stops and then resumes when I return to the page.
Here is my code:
import React, { useEffect, useState } from 'react'
function TimeTracker(props){
const [time, setTime] = useState(0);
const [timerOn, setTimerOn] = useState(false);
useEffect(()=>{
let interval = null;
if(timerOn){
interval = setInterval(()=>{
setTime(prevTime => prevTime +10)
}, 10)
}else{
clearInterval(interval)
}
return ()=> clearInterval(interval)
},[timerOn])
return(
<div>
<p>{("0" + Math.floor((time / 60000) % 60)).slice(-2)} mn</p>
<p>{("0" + Math.floor((time / 1000) % 60)).slice(-2)} s</p>
<button onClick={()=>setTimerOn(true)}>Start</button>
<button onClick={()=>setTimerOn(false)}>Stop</button>
</div>
)
}
Thank you in advance for any help you can give me.
On most browsers inactive tabs have low priority execution and this can affect JavaScript timers.
Thus, when you not focus on your tab, interval will not work. Its not React problem.
Check this question, I wish it help you.
Your counter was not efficient
If you add a console.log before setTime and start the counter you will notice the extra renders
The code below will solve your issue:
import React, { useEffect, useState } from 'react'
function TimeTracker(props){
const [time, setTime] = useState(0);
const [timerOn, setTimerOn] = useState(false);
useEffect(()=>{
let interval = null;
if(timerOn){
interval = setInterval(()=>{
setTime(prevTime => prevTime +1)
}, 1000)
}else{
clearInterval(interval)
}
return ()=> clearInterval(interval)
},[timerOn])
return(
<div>
<p>0{Math.floor(time / 60)} mn</p>
<p>{Math.floor(time % 60)} s</p>
<button onClick={()=>setTimerOn(true)}>Start</button>
<button onClick={()=>setTimerOn(false)}>Stop</button>
</div>
)
}
I am having trouble with following OOP in javascript.
How can I make this code more Object-oriented and reusable?
I tried reading up on OOP concepts in JS but couldn't figure a way around making this code be one. Any suggestions?
PS: This is the code for making a stopwatch
//Define variables to hold time values
let seconds = 0;
let minutes = 0;
let hours = 0;
//Define variable to hold "display" value
let interval = null;
//Define variable to hold the clock status
let status = "paused";
//Clock function ( logic to determine when to increment next value, etc.)
function clock() {
seconds++;
//Logic to determine when to increment next value
if (seconds >= 60) {
seconds = 0;
minutes++;
if (minutes >= 60) {
minutes = 0;
hours++;
}
}
//Display updated time values to user
document.getElementById("display").innerHTML =
//If seconds/minutes/hours are only one digit, add a leading 0 to the value
`${hours ? (hours > 9 ? hours : `0${hours}`) : "00"}:${minutes ? (minutes > 9 ? minutes : `0${minutes}`) : "00"}:${seconds > 9 ? seconds : `0${seconds}`}`;
}
function startPause() {
if (status === "paused") {
//Start the stopwatch (by calling the setInterval() function)
interval = window.setInterval(clock, 1000);
document.getElementById("startPause").innerHTML = "Pause";
status = "started";
} else {
window.clearInterval(interval);
document.getElementById("startPause").innerHTML = "Resume";
status = "paused";
}
}
//Function to reset the stopwatch
function reset() {
seconds = 0;
minutes = 0;
hours = 0;
document.getElementById("display").innerHTML = "00:00:00";
document.getElementById("startPause").innerHTML = "Start";
window.clearInterval(interval);
status = "paused";
}
One could use kind of a "component based" approach. The provided example code is pretty straightforward. The constructor does assign all the needed references as public properties to a Stopwatch instance. Getters and setters for reading and writing the component's UI(/DOM) part are implemented as prototypal methods. Helper methods (interval handling, time measure computations) do not need to be part of the class implementation itself, but will be residents of the Stopwatch module scope ...
// module scope of e.g. 'Stopwatch.js' file.
const MEASURE_STATE_RUNNING = 'running';
const MEASURE_STATE_STOPPED = 'stopped';
const UPDATE_CYCLE_MINIMUM = 100; // any value in msec.
const UPDATE_CYCLE_DEFAULT = 200; //
const UPDATE_CYCLE_MAXIMUM = 1000; //
function getDisplayNumber(value) {
return ((String(value).length === 1) && `0${ value }`) || value;
}
function getMeasureInMilliseconds(measure) {
return (((measure.hours * 3600) + (measure.minutes * 60) + measure.seconds) * 1000);
}
function getMeasureFromMilliseconds(value) {
let hours = (value / 3600000);
let minutes = ((hours - Math.floor(hours)) * 60);
let seconds = ((minutes - Math.floor(minutes)) * 60);
hours = Math.floor(hours);
minutes = Math.floor(minutes);
seconds = Math.floor(seconds + 0.001);
seconds = ((seconds < 60) && seconds) || 0;
minutes = ((minutes < 60) && minutes) || 0;
hours = ((hours < 100) && hours) || 0;
return { hours, minutes, seconds };
}
function handleStartStopForBoundStopwatch(/* evt */) {
const stopwatch = this;
if (stopwatch.measureState === MEASURE_STATE_STOPPED) {
stopwatch.startMeasure();
} else {
stopwatch.stopMeasure();
}
}
function updateStopwatchMeasure(stopwatch) {
const dateNow = Date.now();
// has at least one second past since the last measure update?
const isUpdateMeasure = (Math.floor((dateNow - stopwatch.updateTimestamp) / 1000) >= 1);
if (isUpdateMeasure) {
stopwatch.updateTimestamp = dateNow;
// time differences in milliseconds since measuring has been started the last time.
const timePassed = (dateNow - stopwatch.measureTimestamp);
const messureValue = (timePassed + stopwatch.lastMeasuredMSecs);
Object.assign(stopwatch.measure, getMeasureFromMilliseconds(messureValue));
stopwatch.setComponentMeasure();
}
}
class Stopwatch {
constructor(node) {
this.node = node;
this.timerId = null;
this.updateCycle = this.getComponentUpdateCycle();
// for synchronizing display values of a running measure.
this.lastMeasuredMSecs = null;
this.measureTimestamp = null;
this.updateTimestamp = null;
this.measure = this.getComponentMeasure();
this.measureState = this.getComponentMeasureState();
// synchronize component data initially.
this.setComponentMeasure();
this.setComponentMeasureState();
if (this.measureState === MEASURE_STATE_RUNNING) {
this.startMeasure();
}
this.startStopHandler = handleStartStopForBoundStopwatch.bind(this);
node.addEventListener('click', this.startStopHandler);
}
destroy() {
if (this.node) {
this.node.removeEventListener('click', this.startStopHandler);
this.node.remove();
this.node = null;
delete this.node;
}
this.timerId = this.updateCycle = this.lastMeasuredMSecs = null;
this.measureTimestamp = this.updateTimestamp = null;
this.measure = this.measureState = this.startStopHandler = null;
delete this.timerId;
delete this.updateCycle;
delete this.lastMeasuredMSecs;
delete this.measureTimestamp;
delete this.updateTimestamp;
delete this.measure;
delete this.measureState;
delete this.startStopHandler;
}
getComponentMeasure() {
const result =
(/^(?<hours>\d{1,2})\:(?<minutes>\d{1,2})\:(?<seconds>\d{1,2})$/)
.exec(
this.node.dateTime
);
const {
hours,
minutes,
seconds
} = (result && result.groups) || { hours: 0, minutes: 0, seconds: 0 };
return {
hours: parseInt(hours, 10),
minutes: parseInt(minutes, 10),
seconds: parseInt(seconds, 10)
};
}
setComponentMeasure() {
const { hours, minutes, seconds } = this.measure;
const value = [
getDisplayNumber(hours),
getDisplayNumber(minutes),
getDisplayNumber(seconds)
].join(':');
this.node.dateTime = value;
this.node.innerText = value;
}
getComponentMeasureState() {
return (
((this.node.dataset.measureState || '').trim() === 'running')
&& MEASURE_STATE_RUNNING
|| MEASURE_STATE_STOPPED
);
}
setComponentMeasureState() {
this.node.dataset.measureState = this.measureState;
if (this.measureState === MEASURE_STATE_RUNNING) {
this.node.classList.add(MEASURE_STATE_RUNNING);
this.node.classList.remove(MEASURE_STATE_STOPPED);
} else {
this.node.classList.add(MEASURE_STATE_STOPPED);
this.node.classList.remove(MEASURE_STATE_RUNNING);
}
}
getComponentUpdateCycle() {
let value = parseInt(this.node.dataset.updateCycle, 10);
value = (Number.isNaN(value) && UPDATE_CYCLE_DEFAULT) || value;
return Math.max(UPDATE_CYCLE_MINIMUM, Math.min(UPDATE_CYCLE_MAXIMUM, value));
}
startMeasure() {
this.measureTimestamp = this.updateTimestamp = Date.now();
this.lastMeasuredMSecs = getMeasureInMilliseconds(this.measure);
this.timerId = setInterval(
updateStopwatchMeasure,
this.updateCycle,
this
);
this.measureState = MEASURE_STATE_RUNNING;
this.setComponentMeasureState();
}
stopMeasure() {
clearInterval(this.timerId);
this.lastMeasuredMSecs = null;
this.measureTimestamp = null;
this.updateTimestamp = null;
this.measureState = MEASURE_STATE_STOPPED;
this.setComponentMeasureState();
}/*
resetMeasure() {
Object.assign(this.measure, {
hours: 0,
minutes: 0,
seconds: 0
});
this.setComponentMeasure();
}*/
static initialize(node) {
return new Stopwatch(node);
}
}
/*export default*/function initialize() {
return Array
.from(document.body.querySelectorAll('.stopwatch-component'))
.map(Stopwatch.initialize);
}
/**
* usage
*/
const timerList = initialize();
// console.log('timerList :', timerList);
dd {
margin-bottom: 6px;
}
.stopwatch-component {
font-family: monospace;
font-size: x-large;
cursor: pointer;
}
.stopwatch-component.stopped {
text-decoration: line-through solid #999;
}
<dl>
<dt>
1st time measurement
</dt>
<dd>
<time
class="stopwatch-component"
datetime="0:0:0"
data-update-cycle="100"
data-measure-state="running">0:0:0</time>
</dd>
<dt>
2nd time measurement
</dt>
<dd>
<time
class="stopwatch-component"
datetime="99:59:33"
data-update-cycle="1000">99:59:33</time>
</dd>
<dt>
3rd time measurement
</dt>
<dd>
<time
class="stopwatch-component"
datetime="07:11:55"
data-update-cycle="500"
data-measure-state="stopped">7:11:55</time>
</dd>
</dl>
<p>Click each time measure separately for toggling its pause/proceed state.</p>
Note:
In order to keep the (re)rendering of seconds (minutes, hours) that have been passed in sync, one needs another timer approach than the one provided by the OP.
The examples run with different configurable update cycles defined by the value of the data-update-cycle attribute of a <time class="stopwatch-component"/> element. An update interval of 500msec or just 1sec is not suitable for this kind of measure task, due to setInterval being not precise enough. The running example does demonstrate exactly that.
One has to keep the displayed data in sync by constantly comparing the timestamp from starting a measure process with the timestamp from the current update process. The (re)rendering of a displayed value of cause takes place only if at least a second has passed since the last (re)rendering. But the update cycles which constantly (but not permanently) triggers the (re)rendering has to be run at a higher frequency.
So I want to use requestAnimationFrame to animate something using react hooks.
I want something small so react-spring/animated/react-motion is a bad choice for me.
I implemented useAnimationOnDidUpdate but it is working incorrectly, here is reproduction with details.
What's wrong here: on second click multiplier for animation starts with 1, but should always start with 0 (simple interpolation from 0 to 1).
So I'm trying to understand why the hook saved previous value though I started a new animation loop already.
Here is a full code listing for hook:
import { useState, useEffect, useRef } from 'react';
export function useAnimationOnDidUpdate(
easingName = 'linear',
duration = 500,
delay = 0,
deps = []
) {
const elapsed = useAnimationTimerOnDidUpdate(duration, delay, deps);
const n = Math.min(1, elapsed / duration);
return easing[easingName](n);
}
// https://github.com/streamich/ts-easing/blob/master/src/index.ts
const easing = {
linear: n => n,
elastic: n =>
n * (33 * n * n * n * n - 106 * n * n * n + 126 * n * n - 67 * n + 15),
inExpo: n => Math.pow(2, 10 * (n - 1)),
inOutCubic: (t) => t <.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
};
export function useAnimationTimerOnDidUpdate(duration = 1000, delay = 0, deps = []) {
const [elapsed, setTime] = useState(0);
const mountedRef = useRef(false);
useEffect(
() => {
let animationFrame, timerStop, start, timerDelay;
function onFrame() {
const newElapsed = Date.now() - start;
setTime(newElapsed);
if (newElapsed >= duration) {
console.log('>>>> end with time', newElapsed)
return;
}
loop();
}
function loop() {
animationFrame = requestAnimationFrame(onFrame);
}
function onStart() {
console.log('>>>> start with time', elapsed)
start = Date.now();
loop();
}
if (mountedRef.current) {
timerDelay = delay > 0 ? setTimeout(onStart, delay) : onStart();
} else {
mountedRef.current = true;
}
return () => {
clearTimeout(timerStop);
clearTimeout(timerDelay);
cancelAnimationFrame(animationFrame);
};
},
[duration, delay, ...deps]
);
return elapsed;
}
This problem with this hook is that it doesn't clean up the elapsedTime upon completion.
You can resolve this by adding setTime(0) to you onFrame function when the animation is expected to stop.
Like this:
function onFrame() {
const newElapsed = Date.now() - start;
if (newElapsed >= duration) {
console.log('>>>> end with time', newElapsed)
setTime(0)
return;
}
setTime(newElapsed);
loop();
}
I know it may seem weird that it doesn't reset itself. But bear in mind that your animation is making use of the same hook instance for both easing in and out. Therefore that cleanup is necessary.
Note: I've also move the setTime(newElapsed) line so that it's after the if statement since this isn't necessary if the if statement is true.
UPDATE:
To further improve how this works, you could move the setTime(0) to the return cleanup.
This would mean that you're onFrame function changes to:
function onFrame() {
const newElapsed = Date.now() - start;
if (newElapsed >= duration) {
console.log('>>>> end with time', newElapsed)
setTime(0)
return;
}
setTime(newElapsed);
loop();
}
And then update your return cleanup for useAnimationTimerOnDidUpdate to:
return () => {
clearTimeout(timerStop);
clearTimeout(timerDelay);
cancelAnimationFrame(animationFrame);
setTime(0);
};
I'm assuming that the reason your animation "isn't working properly" was because the component would flash. As far as my testing goes, this update fixes that.
I am making a simple Typescript counter to track my win percentage in my Legends of Runeterra games I play. I can't figure out why when I increment a win or a loss I get NaN as my win percentage. The logic seems fine (obviously you can't decrement right now, that's a problem for later), I just want to focus on fixing the NaN error for now.
Here's my counter component:
import React, { useState } from 'react'
// add a ? after the type name if you want any one of these to be optional, ex: wins?
const Counter: React.FC<{
initialGamesPlayed: number
initialWins: number
initialLosses: number
initialWinPercentage: number
initialDeckName: string
}> = ({
initialDeckName,
initialWinPercentage,
initialWins,
initialLosses,
initialGamesPlayed,
}) => {
const [deckName, setDeckName] = useState(initialDeckName)
const [wins, setWins] = useState(initialWins)
const [losses, setLosses] = useState(initialLosses)
const [totalGames, setTotalGames] = useState(initialGamesPlayed)
const [winPercentage, setWinPercentage] = useState(initialWinPercentage)
const incrementWins = () => {
setWins(wins + 1)
winPercentageCalc()
console.log(winPercentage)
}
const decrementWins = () => {
if (wins > 0) setWins(wins - 1)
winPercentageCalc()
}
const incrementLosses = () => {
setLosses(losses + 1)
winPercentageCalc()
console.log(winPercentage)
}
const decrementLosses = () => {
if (losses > 0) setLosses(losses - 1)
winPercentageCalc()
}
const winPercentageCalc = () => {
setTotalGames(wins + losses)
setWinPercentage((wins / totalGames) * 100)
}
return (
<div>
<p>Deck Name: </p>
<p>wins: {wins} </p>
<button onClick={incrementWins}>+</button>
<button onClick={decrementWins}>-</button>
<p>losses: {losses}</p>
<button onClick={incrementLosses}>+</button>
<button onClick={decrementLosses}>-</button>
<p>Win Percentage: {winPercentage} % </p>
</div>
)
}
export default Counter
Thanks for taking a look!
The setWins, setLosses, setTotalGames and setWinPercentage are all asynchronous functions. So the first time your call winPercentageCalc, this is what happens:
const winPercentageCalc = () => {
setTotalGames(wins + losses) // This is asynchronous, so...
setWinPercentage((wins / totalGames) * 100) // totalGames = 0 => NaN
}
When you divide wins by totalGames, totalGames has not been updated so you divide by 0 which gives NaN (Not a Number) as a result