React - get time of long pressing button in seconds - javascript

I'm trying to return the number of seconds whilst holding in a button.
eg: "click+ hold, inits -> counts & displays 1, 2, 3, 4, 5 -> leaves button -> resets back to 0"
I've gotten close. It works fine, in my console, but whenever I try to update the state it ends up in an infinite loop.
import React, { useState, useEffect } from "react";
const Emergency = () => {
let counter = 0;
let timerinterval;
const [ms, setMs] = useState(counter);
const timer = start => {
console.log("tick tock");
console.log(start);
if (start === true && counter >= 1) {
timerinterval = setInterval(() => {
counter += 1;
console.log(counter);
setMs(counter); //When I remove this, the infinite loop disappears.
}, [1000]);
} else {
setMs(0);
}
};
const pressingDown = e => {
console.log("start");
e.preventDefault();
counter = 1;
timer(true);
};
const notPressingDown = e => {
console.log("stop");
e.preventDefault();
timer(false);
setMs(0);
clearInterval(timerinterval);
};
return (
<>
<button
onMouseDown={pressingDown}
onMouseUp={notPressingDown}
onTouchStart={pressingDown}
onTouchEnd={notPressingDown}
className="button is-primary mt-3"
>
Emergency
</button>
<br />
Time holding it is.... {ms}
</>
);
};
export default Emergency;
An easy way would be to calculate the time difference between mouseDown and mouseUp, but for the sake of UX, I would like to {ms} to update live as I'm holding the button.
Any suggestions?
Thanks!

There are two problems with your code:
You are not clearing interval. timeInterval is a new variable whenever your component is re-rendered. You need to use ref (const timeInterval = React.useRef(null); ... timeInterval.current = ... ; clearInterval(timeInterval.current);
Also you need to remove counter = 1; from your pressingDowm function, because before each setMs you are incrementing it by one
const Emergency = () => {
let counter = 0;
let timerinterval = React.useRef((null as unknown) as any);
const [ms, setMs] = React.useState(counter);
const timer = (start: any) => {
console.log('tick tock');
console.log(start);
if (start === true && counter >= 1) {
timerinterval.current = setInterval(() => {
console.log(counter);
setMs(counter); //When I remove this, the infinite loop disappears.
counter += 1;
//#ts-ignore
}, [1000]);
} else {
setMs(0);
}
};
const pressingDown = (e: any) => {
console.log('start');
e.preventDefault();
counter = 1;
timer(true);
};
const notPressingDown = (e: any) => {
console.log('stop');
e.preventDefault();
timer(false);
setMs(0);
clearInterval(timerinterval.current);
};
return (
<>
<button
onMouseDown={pressingDown}
onMouseUp={notPressingDown}
onTouchStart={pressingDown}
onTouchEnd={notPressingDown}
className="button is-primary mt-3"
>
Emergency
</button>
<br />
Time holding it is.... {ms}
</>
);
};
This is edited code (with some TypeScript stuff, sorry for that)

Related

React clearInterval not working in class Component

I am trying to make a timer that will start at the beginning of the sorting visualization and run until sorting is over. The timer starts accordingly but does not stop after sorting. this works absolutely fine in functional Component (with useEffect hook) but does not work in class Component.
here is my startTimer function -
startTimer = () => {
let isAlgo = this.state.isAlgorithmSortOver;
let interval;
if (isAlgo === true) {
interval = setInterval(() => {
this.setState({
time: this.state.time + 10,
});
}, 10);
} else if (isAlgo === false) {
clearInterval(interval);
}
// return () => clearInterval(interval);
};
and here is startVisualizer function -
startVisualizer = () => {
let steps = this.state.arraySteps;
let colorSteps = this.state.colorSteps;
this.clearTimeouts();
let timeouts = [];
let i = 0;
while (i < steps.length - this.state.currentStep) {
let timeout = setTimeout(() => {
let currentStep = this.state.currentStep;
this.setState({
array: steps[currentStep],
colorElement: colorSteps[currentStep],
currentStep: currentStep + 1,
isAlgorithmSortOver: false,
});
//? comparing the currentStep with arraySteps and the state of isAlgorithmSortOver will remain false until the array is fully sorted.. Adding '+ 1' to currentStep because the arraySteps state always will be '+1' bigger than the currentStep..
if (currentStep + 1 === i) {
this.setState({
isAlgorithmSortOver: true,
});
}
timeouts.push(timeout);
}, this.state.delayAnimation * i);
i++;
}
this.startTimer();
this.setState({
timeouts: timeouts,
// isAlgorithmSortOver: false,
});
};
because you are not clearing the interval. Try to save interval's id to state like this:
startTimer = () => {
let isAlgo = this.state.isAlgorithmSortOver;
if (isAlgo === true) {
interval = setInterval(() => {
this.setState({
time: this.state.time + 10,
});
}, 10);
this.setState({interval})
}
};
Then you can then call clearInterval wherever you want, like this:
clearInterval(this.state.interval)
You also should bring out timeouts.push(timeout) from setTimeout where in while. Because, otherwise timeouts.push(timeout) does not work synchronously, it works after a while.

How to create countup timer with keyboard events in js

I am creating a program where the timer starts when i hit "keyup" and stops when i hit "keydown" and resets the timer when i hit "keydown" next time.
const Timer = () => {
const [timer, setTimer] = useState(0);
let state = 0;
let isFired = false;
const increment = useRef(null);
useEffect(() => {
window.addEventListener('keydown', (e) => {
if (isFired) {
if (e.code === 'Space' && state === 2) {
e.stopPropagation();
isFired = false;
handleReset();
}
if (e.code === 'Space' && state === 1) {
clearInterval(increment.current);
state = 2;
}
}
});
window.addEventListener('keyup', (e) => {
if (!isFired) {
if (e.code === 'Space' && state === 0) {
isFired = true;
state = 1;
handleStart();
}
}
});
return () => {
window.removeEventListener('keydown', handleReset);
window.removeEventListener('keyup', handleStart);
};
}, []);
const handleStart = () => {
increment.current = setInterval(() => {
// DON'T COPY THIS BIT
setTimer((timer) => timer + 10);
}, 10);
};
const handleReset = () => {
clearInterval(increment.current);
setTimer(0);
state = 0;
};
// DON'T COPY THIS BIT
const formatTime = () => {
console.log(timer);
const getMilliSeconds = `0${timer % 60}`.slice(-2);
const seconds = `${Math.floor(timer / 60)}`;
const getSeconds = `0${seconds % 60}`.slice(-2);
const getMinutes = `0${Math.floor(timer / 3600)}`.slice(-2);
// return `${getHours} : ${getMinutes} : ${getSeconds}`;
if (getMinutes === '00') {
return `${getSeconds}.${getMilliSeconds}`;
} else {
return `${getMinutes} : ${getSeconds} : ${getMilliSeconds} `;
}
};
// const formatTime = () => {
// // const milliSeconds = `0${}`;
// const seconds = `0${Math.floor(timer / 100)}`;
// const minute = `0${Math.floor(timer / 3600)}`.slice(-2);
// return `${minute}.${seconds}`;
// };
return (
<div className="Timer">
<h1>{formatTime()}</h1>
</div>
);
};
i have tried this so far
it works most of the time but sometimes it gets glitchy
and i know the time is not formated properly and also the increment is also wrong. Sometimes it stops on the "keydown" and fires the "keyup" in the same stroke making it reset the timer starting from zero I don't exactly know that if it fires keyup on the same stroke but it seems like it does
this is a link to what i have done so far timer
I checked your code and found it needs too many changes.
You are using state and isFired as variables only but you need their persisted values on rerendering. Therefore, these must be states.
You are not unbinding the same listener which you binded on keyup and keydown.
As per your current code, your timer will start on 1st keyup and then next keydown it will stop as such where it was. Now, on next keydown it will reset to 0 and then on keyup, again timer will start from 0.
I modified your code and see working changes here,
https://codesandbox.io/s/xenodochial-shannon-im8zt?file=/src/App.js

React timer does not update when there are other re-renders going on

I have a typing test web app built with a timer and text for the user to type. The user input is recorded via event listeners in a useEffect hook. The timer also uses an useEffect hook. Everything works just fine when they are separate, but when they are together and the user types the text, the timer is paused and does not rerender. It's not a delay. Not sure why this is happening, below is the code for the timer.
const Timer = ({initMin, initSec, setFin}) => {
const [ minutes, setMinutes ] = useState(parseInt(initMin));
const [ seconds, setSeconds ] = useState(parseInt(initSec));
// const [ timeEnd, setTimeEnd ] = useState(false);
useEffect(() => {
const myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds( old => old - 1 );
}
if (seconds === 0) {
if (minutes === 0) {
setFin(true); // Ends the game
clearInterval(myInterval)
} else {
setMinutes( old => old - 1 );
setSeconds(59);
}
}
}, 1000)
return () => { clearInterval(myInterval); };
});
return (
<h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds} </h1>
);
}
And for the typing component.
export default function TypeTest({setStlLst, word}) {
const [test1Arr, setTest] = useState(word.split(' '));
const tempStyles = [];
for (var i = 0; i < test1Arr.length; ++i){
var letterStyles = [];
for (var j = 0; j < test1Arr[i].length; ++j){
if (!i && !j) letterStyles.push('current');
letterStyles.push('');
}
tempStyles.push(letterStyles);
}
const [styles, setStyles] = useState(tempStyles);
const [currWord, setWord] = useState(0);
const [currChar, setChar] = useState(0);
const [prevPos, setPrev] = useState(0);
useEffect(() => {
setStlLst(styles);
const alterStyle = (e) => {
var temp = styles.slice();
test1Arr[0][0] === e.key ? temp[0][0] = 'correct' : temp[0][0] = 'incorrect';
setStyles(temp);
}
window.addEventListener("keydown", alterStyle);
return () => window.removeEventListener("keydown", alterStyle);
}, [currChar, currWord, styles, prevPos, test1Arr, setStlLst]);
return (
<div className='outer-word-container'>
<div className="word-container">
{test1Arr.map((monoWord, idx) => <Word key={idx} inputWord = {monoWord} letterStyles={styles[idx]}/>)}
</div>
</div>
)
}
Any guidance would be appreciated!

using setInterval to increment variable every second - not updating

Inside my functional component I have defined two hooks started:false and sec:0
let interval = null
const Home = () => {
const [sec, setSec] = useState(0)
const [started, setStarted] = useState(false)
So as the name suggests, every second I want to increment this counter.
I have method called setTimer which should increment my sec every second.
function setTimer() {
console.log(started)
if (!started) {
console.log(started)
setStarted(true);
interval = setInterval(() => {
setSec(sec+1)
console.log("ADDED",sec)
}, 1000)
}
}
But it seems that the sec counter never goes above 1. Is there a reason for this?
You should uses a functional state update, so instead of setSec(sec+1) write setSec(prevSec => prevSec + 1)
See the React Hooks API for reference: https://reactjs.org/docs/hooks-reference.html#usestate
let started = false;
let sec = 0;
let setSec = function(seconds) { sec = seconds; }
let setStarted = function() { started = true; }
function setTimer() {
console.log(started)
if (!started) {
setStarted(true);
console.log(started)
interval = setInterval(() => {
setSec(sec+1)
console.log("ADDED",sec)
}, 1000)
}
}
setTimer();
var setStarted = false;
setSec = 0;
function setTimer() {
console.log(setStarted)
if (!setStarted) {
console.log(setStarted)
setStarted = true;
setInterval(() => {
setSec +=1;
console.log("ADDED",setSec);
}, 1000)
}
}
setTimer();
setInterval has closed over the initial value of your state(sec). Every time you are modifiying it, you are doing setSec(0+1), essentially making it 1. This is the problem of stale state.
You can use useRef to have access to the current value always.
import "./styles.css";
import { useState, useRef } from "react";
export default function App() {
const [sec, setSec] = useState(0);
const [started, setStarted] = useState(false);
let interval = null;
let realSec = useRef(0);
function setTimer() {
console.log(started);
if (!started) {
console.log(started);
setStarted(true);
interval = setInterval(() => {
setSec(realSec.current + 1);
realSec.current++;
console.log("ADDED", sec);
}, 1000);
}
}
return (
<>
<p>{sec}</p>
<button onClick={setTimer}>X</button>
</>
);
}

Fully functioning countdown timer using React hooks only

Here's my code. You can also check it out on Stackblitz:
import React, { useState } from 'react';
const Timer = ({
initialHours = 10,
initialMinutes = 0,
initialSeconds = 0,
}) => {
const [hours, setHours] = useState(initialHours);
const [minutes, setMinutes] = useState(initialMinutes);
const [seconds, setSeconds] = useState(initialSeconds);
let myInterval;
const startTimer = () => {
myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
if (seconds === 0) {
if (hours === 0 && minutes === 0) {
clearInterval(myInterval);
} else if (minutes > 0) {
setMinutes(minutes - 1);
setSeconds(59);
} else if (hours > 0) {
setHours(hours - 1);
setMinutes(59);
setSeconds(59);
}
}
}, 1000);
cancelTimer();
};
const cancelTimer = () => {
return () => {
clearInterval(myInterval);
};
};
return (
<div>
<h1 className='timer'>
{hours < 10 && hours !== 0 ? `0${hours}:` : hours >= 10 && `${hours}:`}
{minutes < 10 ? `0${minutes}` : minutes}:
{seconds < 10 ? `0${seconds}` : seconds}
</h1>
<button onClick={startTimer}>START</button>
<button>PAUSE</button>
<button>RESUME</button>
<button onClick={cancelTimer}>CANCEL</button>
</div>
);
};
export default Timer;
I'm having trouble with the START button and this is what it looks like when I click on the START button multiple times:
You'll notice that on the first click the number never continues to go down unless I click on the START button again and again but it would look like a broken slot machine. And if I hit on the CANCEL button, it should stop the timer and reset back to the set time, but it doesn't. I don't know how to solve the problem for this 2 buttons, and much more for the PAUSE and RESUME. I don't know how to make them work, too. Please help.
As suggested by #Felix Kling you can try a different approach, to check why your code is not working check the below code, I've made some changes in your Timer component :
import React, { useState } from 'react';
const Timer = ({
initialHours = 10,
initialMinutes = 0,
initialSeconds = 0,
}) => {
const [time, setTime] = useState({
h: initialHours,
m: initialMinutes,
s: initialSeconds,
});
const [timer, setTimer] = useState(null);
const startTimer = () => {
let myInterval = setInterval(() => {
setTime((time) => {
const updatedTime = { ...time };
if (time.s > 0) {
updatedTime.s--;
}
if (time.s === 0) {
if (time.h === 0 && time.m === 0) {
clearInterval(myInterval);
} else if (time.m > 0) {
updatedTime.m--;
updatedTime.s = 59;
} else if (updatedTime.h > 0) {
updatedTime.h--;
updatedTime.m = 59;
updatedTime.s = 59;
}
}
return updatedTime;
});
}, 1000);
setTimer(myInterval);
};
const pauseTimer = () => {
clearInterval(timer);
};
const cancelTimer = () => {
clearInterval(timer);
setTime({
h: initialHours,
m: initialMinutes,
s: initialSeconds,
});
};
return (
<div>
<h1 className='timer'>
{time.h < 10 && time.h !== 0
? `0${time.h}:`
: time.h >= 10 && `${time.h}:`}
{time.m < 10 ? `0${time.m}` : time.m}:
{time.s < 10 ? `0${time.s}` : time.s}
</h1>
<button onClick={startTimer}>START</button>
<button onClick={pauseTimer}>PAUSE</button>
<button onClick={cancelTimer}>CANCEL</button>
</div>
);
};
export default Timer;
Explanation:
in your startTimer function in the last line you're calling cancelTimer
When you're working with hooks then keep in mind you won't get updated value of state variable until you use function inside a set function like I'm doing in setTime and in that callback, you'll get an updated value as a first parameter
In cancelTimer method you're returning a function you've to call clearInterval also myInterval is undefined in cancelTimer so I've set it's value in state
For more information and other ways check this question

Categories

Resources