Hello mighty people from internet,
I am trying to build a countdown timer app with pause and reset buttons using React hooks.
The timer countdown should stop once pause or reset button gets clicked performed by function pauseCountDown() and reset(), respectively.
However, the timer does not stop after pauseCountDown() or reset() executed.
I don’t quite understand why setInterval() does not stop after clearInterval() gets executed.
clearInterval() should be executed when isPaused === true.
However, the value of “isPaused” instantly switches back to false along with the next iteration of nonstopped setInterval().
Any ideas or thoughts what I missed or mistakes I have?
Thank you.
Here is the code of the child component performing timer countdown:
function TimerPanel(props) {
var launch;
var secLeft = parseInt(props.timerNow.min) * 60 + parseInt(props.timerNow.sec)
const startCountDown = () => {
props.setIsPaused(false)
launch = setInterval(decrement, 1000)
}
function decrement() {
if (secLeft > 0 && !props.isPaused) {
secLeft--;
var minCountDown = Math.floor(secLeft / 60)
var secCountDown = Math.floor((secLeft % 60) * 100) / 100
props.setTimerNow({...props.timerNow, min: minCountDown, sec: secCountDown})
}
}
const pauseCountDown = () => {
props.setIsPaused(true)
clearInterval(launch)
}
const reset = () => {
props.setIsPaused(false)
clearInterval(launch)
props.setSessionLength(props.initialTimer.min);
props.setTimerNow({...props.timerNow, min: props.initialTimer.min, sec: props.initialTimer.sec})
}
Since launch is just an ordinary variable, its value will not survive across rerenderings of TimerPanel. You'll need to store it inside a ref with useRef
// add useRef to your imports from react
import { useRef } from "react";
function TimerPanel(props) {
const launch = useRef();
var secLeft =
parseInt(props.timerNow.min) * 60 + parseInt(props.timerNow.sec);
const startCountDown = () => {
props.setIsPaused(false);
clearInterval(launch.current) // clean up any old timers
launch.current = setInterval(decrement, 1000);
};
function decrement() {
if (secLeft > 0 && !props.isPaused) {
secLeft--;
var minCountDown = Math.floor(secLeft / 60);
var secCountDown = Math.floor((secLeft % 60) * 100) / 100;
props.setTimerNow({
...props.timerNow,
min: minCountDown,
sec: secCountDown,
});
}
}
const pauseCountDown = () => {
props.setIsPaused(true);
clearInterval(launch.current);
};
const reset = () => {
props.setIsPaused(false);
clearInterval(launch.current);
props.setSessionLength(props.initialTimer.min);
props.setTimerNow({
...props.timerNow,
min: props.initialTimer.min,
sec: props.initialTimer.sec,
});
};
// ...
}
Related
when i click my button, a timer is supposed to display a countdown timer. But the button does not work.
let timerCounter = document.getElementById("timer-counter");
let timer;
let timerCount;
function startTimer() {
timer = setInterval(function() {
timerCount--;
timerElement.textContent = "Time; " + timerCount;
if (timerCount === 0) {
clearInterval(timer);
}
});
}
startButton.addEventListener("click", startTimer);
This is what I found so far:
You are decrementing the timerCount, need to specify the initial value for it to work.
You're using timerElement instead of timerCounter that you've declared.
You must pass the second args to the setInterval which is delay.
const timerCounter = document.getElementById('timer-counter');
const startButton = document.getElementById('start-button');
let timer;
let timerCount = 30;
startButton.addEventListener('click', startTimer);
function startTimer() {
timer = setInterval(function () {
timerCount--;
timerCounter.textContent = 'Time; ' + timerCount;
if (timerCount === 0) {
clearInterval(timer);
}
}, 1000);
}
<div id="timer-counter"></div>
<button id="start-button">Start</button>
Here's a slightly different approach that avoids some of the problems with global variables. The function the listener calls initialises the count, and then returns a new function (a closure) that is called when the button is clicked. It also uses setTimeout which I find more easy to understand.
// Cache your elements
const counter = document.querySelector('#counter');
const startButton = document.querySelector('button');
// Initialise your count variable
function startTimer(count = 30) {
// Return a function that is called from
// the listener
return function loop () {
// Disabled the button once it's been clicked
if(!startButton.disabled) startButton.disabled = true;
counter.textContent = `Time: ${count}`;
if (count > 0) {
setTimeout(loop, 500, --count);
}
}
loop();
}
// Call startTimer to initialise the count, and return
// a new function that is used as the listener
startButton.addEventListener('click', startTimer(), false);
<div id="counter"></div>
<button>Start</button>
I'm sure this could be improved.
In this example we don't go below 0.
We don't allow timeout collisions ( timeouts don't stack causing weird counting speeds ).
We can reset to the original number when on 0.
const c = document.getElementById('timer-counter')
const b = document.getElementById('start-button')
let timer = false
let timerCount = 30
b.addEventListener('click', start)
function decrement() {
if(timerCount < 0) {
timerCount = 30
timer = false
return
}
c.innerText = `Count: ${timerCount}`
timerCount--
timer = setTimeout(decrement, 200)
}
function start() {
if(timer) return
decrement()
}
<div id="timer-counter"></div>
<button id="start-button">Start</button>
I created a timer function it's working but when I click clearInterval it's not working, timer still going on.
Here is my function to start timer. Maximum limit for timer is 60sec
const StartRecord = ()=>{
const timeout = setInterval(() => {
if (time != 60) {
setTime(prevState => prevState + 1);
}
}, 1000);
console.log(timeout);
if (time == 60) {
clearInterval(timeout);
}
}
here is my function to stop timer
const onStopRecord = () => {
clearInterval(time);
}
can anyone tell me why it's not working?
You are trying clearInterval using time. It should be timeout
const onStopRecord = () => {
clearInterval(timeout);
}
you can use useEffect to do this task
useEffect(() => {
let interval
if(time != 60){
interval = setInterval(() => setTime((prev) => prev + 1), 1000);
}
return () => clearInterval(interval);
}, [time])
I am practically new to React. In this App I am using Hooks!
I've made a Countdown Timer that will show in a few seconds after logging in. I am unable to make it stop on a button click. I need some advise on this as I've been struggling for the past 2 days with this.
This is my code so far: (Please Help)
function Admin() {
const [isTimerOpen, setTimmer] = useState(false);
let history = useHistory();
// SET BY THE ADMIN
var minutesToCountDown = 0.9;
// TRANSFORM INTO SECONDS
var transformMinutesToSeconds = minutesToCountDown * 60
// KEEP A STATE
const [counterValue, setCounterValue] = useState(0);
const [isTimmerStoped, setStopTimer] = useState(false);
// FUNCTION TO HAPPEN EVERY 1 SECOND
function timeIt() {
if (isTimmerStoped === false) {
transformMinutesToSeconds--
setCounterValue(transformMinutesToSeconds)
console.log("Timer is on: ", transformMinutesToSeconds)
if (transformMinutesToSeconds === 0) {
clearInterval(interval)
setStopTimer(true)
}
} else {
setStopTimer(true)
clearInterval(interval)
}
}
// STARTS THE COUNTDOWN
var interval;
const startCountdown = () => {
interval = setInterval(timeIt, 1000)
}
const stopCountdown = () => {
console.log("Stop Timer")
setStopTimer(true);
setCounterValue(0);
setTimmer(false);
}
// ADD 0 IN FRONT ON THE TIME REMAINING
const addLeadingZeros = value => {
value = String(value);
while (value.length < 2) {
value = `0${value}`;
}
return value;
};
// CONVERT SECONDS INTO TIME REMAINING
function convertSeconds(seconds) {
var min = Math.floor(seconds / 60);
var sec = seconds % 60;
return addLeadingZeros(min) + ':' + addLeadingZeros(sec)
}
const logOutUser = () => {
logout();
return history.push(mainRoute)
}
function setTimer() {
const timer = setTimeout(() => {
setTimmer(true)
console.log('This will run after 3 seconds!')
startCountdown()
}, sessionTimeout);
return () => clearTimeout(timer);
}
useEffect(() => {
if (isTimmerStoped === false) {
console.log('Effect Starting', isTimmerStoped)
setTimer()
} else {
console.log('Effect Stopping', isTimmerStoped)
stopCountdown()
}
}, [isTimmerStoped, setStopTimer, minutesToCountDown]);
return <React.Fragment>
<CssBaseline />
<Container disableGutters maxWidth={false}>
<NavigationBar handleSignOut={logOutUser}/>
<TimerContent
timeRemaining={convertSeconds(counterValue)}
isTimerAlertOpen={isTimerOpen}
extendSessionBtn={stopCountdown}
logoutBtn={logOutUser}
clickOutsideButton={stopCountdown}/>
</Container>
</React.Fragment>
}
export default Admin;
You should do 2 things.
Make the interval variable a ref. This way it's value will be unique every where it imported. Note: Just creating a variable above the component is a bad idea because that variable will be shared between each component that imports the Admin component, which will lead to bugs.
Wrong
let interval;
function Admin() {
//... code here
// STARTS THE COUNTDOWN
// var interval; Remove from here
const startCountdown = () => {
interval = setInterval(timeIt, 1000)
}
//... code here
}
export default Admin;
Right
function Admin() {
const interval = React.useRef();
//... code here
// STARTS THE COUNTDOWN
// var interval; Remove from here
const startCountdown = () => {
interval.current = setInterval(timeIt, 1000)
}
//... code here
}
export default Admin;
Add clearInterval to your stopCountdown function. You can remove the clearInterval in the timeIt function and move it into stopCountdown. Please take a look at this codepen I made to demostrate
const stopCountdown = () => {
console.log("Stop Timer")
setStopTimer(true);
setCounterValue(0);
setTimmer(false);
clearInterval(interval.current) // Clear the interval here
}
You shouldn't maintain the intervalId returned by setInterval in a functional variable since it will be reset on re-render and you will loose the actual timer Id. You must instead use useRef hook to store the timerId
const interval = useRef(null);
...
function timeIt() {
if (isTimmerStoped === false) {
transformMinutesToSeconds--
setCounterValue(transformMinutesToSeconds)
console.log("Timer is on: ", transformMinutesToSeconds)
if (transformMinutesToSeconds === 0) {
clearInterval(interval.current)
setStopTimer(true)
}
} else {
setStopTimer(true)
clearInterval(interval.current)
}
}
...
// STARTS THE COUNTDOWN
const startCountdown = () => {
interval.current = setInterval(timeIt, 1000)
}
I am working on knockout js.
In that i have a recursive function which executes a function every minute. for that am using a timer every 60 sec it will execute also same will be reflecting in the UI also.
In my case, if i try to assign or initialize a timer value(observable) which is inside a loop, it doesn't reflecting instead of reflecting it is added to the pipeline and that much time loop is running simultaneously.
In that case i want to kill the loop and again want to restart every time i am changing the timer value.
timerInSec=60;
var loop = function () {
if (this.timer() < 1) {
myFunction()
this.timer(this.timerInSec - 1);
setTimeout(loop, 1000);
} else {
this.timer(this.timer() - 1);
setTimeout(loop, 1000);
}
};
loop();
Here is my solution. Please check.
timerInSec = 60;
const Loop = (function () {
let timer = 0;
let timerId = -1;
const myFunction = function () {
console.log('finished');
}
const fnLog = function (tm) {
console.log('current time = ', tm);
}
const fnProc = function () {
timerId = setTimeout(myFunction, 1000 * timer);
}
return {
start: function (tm = 60) {
this.stop();
timer = tm;
fnProc();
},
stop: function () {
if (timerId !== -1) {
clearTimeout(timerId);
timerId = -1;
}
}
}
})();
Loop.start(timerInSec);
setTimeout(() => {
Loop.start(timerInSec);
}, 500);
I'm working on a little "web app" for a quiz.
Each slide has got a certain amount of time to be answered (or 0 to infinite time).
I find JS here to do the countdown:
function Countdown(options) {
var timer,
instance = this,
seconds = options.seconds || 10,
updateStatus = options.onUpdateStatus || function () {},
counterEnd = options.onCounterEnd || function () {};
function decrementCounter() {
updateStatus(seconds);
if (seconds === 0) {
counterEnd();
instance.stop();
}
seconds--;
}
this.start = function () {
clearInterval(timer);
timer = 0;
seconds = options.seconds;
timer = setInterval(decrementCounter, 1000);
};
this.stop = function () {
clearInterval(timer);
};
}
var myCounter = new Countdown({
seconds: timetogo, // number of seconds to count down
onUpdateStatus: function (sec) {
elapsed = timetogo - sec;
$('.progress-bar').width(((elapsed / timetogo) * 100) + "%");
}, // callback for each second
onCounterEnd: function () {
//alert('counter ended!');
} // final action
});
myCounter.start();
I made a jsfiddle here :
https://jsfiddle.net/mitchum/kz2400cc/2/
But i am having trouble when you go to the next slide, the progress bar "bump".
after looking into "live source panel from chrome" I saw it's like the first "counter" is not stopped and still runs.
Do you have any tips or hint to help me to solve my bug ?
Thanks
You must pay attention to the scope of the variables. I change the "var myCounter" under document ready in "var myCounterFirst". Check the updated JSFiddle.
var timetogoFirst = $('.current').attr("data-time");
var myCounterFirst = new Countdown({
seconds: timetogoFirst, // number of seconds to count down
onUpdateStatus: function (sec) {
elapsed = timetogoFirst - sec;
$('.progress-bar').width(((elapsed / timetogoFirst) * 100) + "%");
}, // callback for each second
onCounterEnd: function () {
alert('counter ended!');
} // final action
});
myCounterFirst.start();