This question already has answers here:
Cancel/kill window.setTimeout() before it happens
(2 answers)
Closed 1 year ago.
I have a count Down timer which start automatically .
I would like to restart it when time finish with click on a button , actually in a certain time one of time or button is appear
problems : clearInteraval is not working , timeInteraval works in 500 not in 1000
Can anyone Help?
I hope you'll understand what I have said;
constructor(props){
super(props);
this.state = {
time : [0,0,4],
date : new Date(),
counterStop :false,
};
// autoplay countdown counter
this.startCounter();
}
this is main function of timer , I use setState in this function
countdown = () =>{
let hr = this.state.time[0];
let mm = this.state.time[1];
let ss = this.state.time[2];
if(hr == 0 && mm == 0 && ss == 0 || mm<0){
this.finishCounter();
this.setState({counterStop:true})
}
ss--;
if(ss < 0)
{
ss = 59;
mm--;
if(mm == 0)
{
mm = 59;
hr--;
}
}
if(hr.toString().length < 2) hr = "0"+hr;
if(mm.toString().length < 2) mm = "0"+mm;
if(ss.toString().length < 2) ss = "0"+ss;
this.setState({
time : [hr,mm,ss]
})
}
clearInterval not work here and I define time because state has changed in countDown timer
finishCounter = () => {
clearInterval(this.startCounter);
this.setState({
time : [0,0,4]
})
console.log('finishCounter')
}
startCounter =()=> {
this.setState({time:[0,0,4],counterStop:false})
setInterval(this.countdown, 1000) ;
}
class component rendering
render(){
return(
<div>
{
this.state.counterStop
? <button onClick={this.startCounter.bind(this)} >start counter</button>
: <p>{`${this.state.time[0]} : ${this.state.time[1]} : ${this.state.time[2]}`}</p>
}
<button onClick={this.finishCounter.bind(this)}>stop</button>
</div>
)
}
To make #jabaa's suggestions into an explicit solution for your case of a React class component, try this:
finishCounter = () => {
clearInterval(this.interval);
this.setState({
time : [0,0,4]
})
console.log('finishCounter')
}
startCounter = () => {
this.setState({time:[0,0,4],counterStop:false})
this.interval = setInterval(this.countdown, 1000);
}
Related
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
I've been working on an alarm clock 'app' project to practice JavaScript. I'm fairly new to it so maybe I'm just not understanding clearInterval() correctly, and I was hoping someone could help.
JavaScript:
let sound = new Audio('./music/alarmsound.mp3');
let playAudio = () => sound.play();
const snooze = document.getElementById('snooze');
let pause = () => sound.pause();
let alarm = document.getElementById('alarm');
alarm.onclick = function setAlarm() {
let userHour = prompt("Please enter the hour of which you would like the alarm to be set 😄", "07");
if (userHour.charAt(0) > 0 && userHour < 10) {
userHour = "0" + userHour;
}
let userMinutes = prompt("Please enter the minutes of which you would like the alarm to be set 😄", "30");
let timeformat = [userHour, userMinutes];
function realTime() {
let realtime = new Date();
if(timeformat[0] == realtime.getHours() && timeformat[1] == realtime.getMinutes()) {
playAudio();
}
if(timeformat[0] != realtime.getHours && timeformat[1] != realtime.getMinutes()) {
pause();
}
}
let checkTime = () => {
setInterval(realTime, 1000)
}
checkTime();
let stopAlarm = () => {
clearInterval(checkTime);
}
snooze.addEventListener("click", stopAlarm());
}
When the user clicks the alarm button, a prompt asks for them to set the hours, and then the minutes, that they want the alarm to go off. That part works. In addition, once one minute has passed and the current time no longer matches the alarm time set by the user, the audio stops. However, I'm trying to add a snooze button functionality and I can't seem to get it working no matter what I try.
Any tips and tricks are greatly appreciated! Sorry if the code is messy, like I said, I just started with JS.
A few thing are making this not work as you expect. Check the suggested changes, I've added comments.
const snooze = document.getElementById('snooze');
const alarm = document.getElementById('alarm');
const sound = new Audio('./music/alarmsound.mp3');
const playAudio = () => sound.play();
const pause = () => sound.pause();
let intval; // 1. keep a reference to the interval outside of setAlarm()
alarm.onclick = function setAlarm() {
let userHour = prompt("Please enter the...", "07");
if (userHour.charAt(0) > 0 && userHour < 10) {
userHour = "0" + userHour;
}
let userMinutes = prompt("Please enter the...", "30");
let timeformat = [userHour, userMinutes];
function realTime() {
let realtime = new Date();
if(timeformat[0] == realtime.getHours() && timeformat[1] == realtime.getMinutes()) {
playAudio();
}
if(timeformat[0] != realtime.getHours && timeformat[1] != realtime.getMinutes()) {
pause();
}
}
intval = setInterval(realTime, 1000) // 2. assign interval to the variable
}
// 3. Dont set your event listener inside the setAlarm() function (unless it's required)
function stopAlarm() {
clearInterval(intval); // 4. Clear interval with correct reference
}
snooze.addEventListener("click", stopAlarm);
I'm trying to build a React component that has two timers. The first one is three seconds, and when it hits zero it says "GO!" then immediately starts the next one, which is a 60 second timer. I got all of this to work, but I can't figure out how to stop the 60 second timer from starting over when it hits zero. Ideally I will be splitting these into two components once I figure this out, but right now I just want to get everything to work properly. Can someone steer me in the right direction here?
import React, { Component } from "react";
import ReactDOM from "react-dom"
class Countdown extends React.Component {
constructor(props) {
super(props);
this.state = { time: {}, seconds: 3 };
this.timer = 0;
this.startTimer = this.startTimer.bind(this);
this.countdown = this.countdown.bind(this);
}
formatSeconds (totalSeconds) {
let seconds = totalSeconds;
let minutes = Math.floor(totalSeconds / 60);
if (seconds < 10) {
seconds = '0' + seconds;
}
if (minutes < 10) {
minutes = '0' + minutes;
}
let obj = {
"minutes": minutes,
"seconds": seconds
};
return obj;
}
componentDidMount() {
let timeLeft = this.formatSeconds(this.state.seconds);
this.setState({ time: timeLeft });
}
startTimer() {
if (this.timer === 0) {
this.timer = setInterval(this.countdown, 1000);
}
}
countdown() {
let seconds = this.state.seconds - 1;
this.setState({
time: this.formatSeconds(seconds),
seconds: seconds
});
if (seconds === 0) {
clearInterval(this.timer);
let element = (
<h2>GO!</h2>
);
ReactDOM.render(
element,
document.getElementById('go')
);
this.state.seconds = 61;
let seconds = this.state.seconds - 1;
this.timer = setInterval(this.countdown, 1000);
this.setState({
time: this.formatSeconds(seconds),
seconds: seconds
});
// how do I stop the second timer from looping?
}
}
render() {
return(
<div className="container">
<div className="row">
<div className="column small-centered medium-6 large-4">
<h2 id="go"></h2>
{this.state.time.seconds}<br />
<button className="button" onClick={this.startTimer}>Start</button>
</div>
</div>
</div>
);
}
}
export default Countdown;
Could you break it out into two countdown functions as follows?
class Countdown extends React.Component {
constructor(props) {
super(props);
this.state = {
countdownSeconds: 3,
roundSeconds: 60
};
this.countdown = this.countdown.bind(this);
this.roundCountdown = this.roundCountdown.bind(this);
this.startTimer = this.startTimer.bind(this);
}
startTimer() {
if(!this.countdownTimer) {
this.countdownTimer = setInterval(this.countdown, 1000);
}
}
countdown() {
const currentSeconds = this.state.countdownSeconds - 1;
this.setState({
countdownSeconds: currentSeconds
});
if(currentSeconds === 0) {
this.roundTimer = setInterval(this.roundCountdown, 1000);
clearInterval(this.countdownTimer);
}
}
roundCountdown() {
const currentSeconds = this.state.roundSeconds - 1;
this.setState({
roundSeconds: currentSeconds
});
if(currentSeconds === 0) {
//do whatever
clearInterval(this.roundTimer);
}
}
}
Edit:
If I understand your other question, to display the appropriate countdown you could maybe do something like
render() {
const { countdownSeconds, roundSeconds } = this.state;
return (
<div>
{countdownSeconds > 0 &&
<span>Counting down...{countdownSeconds}</span>
}
{roundSeconds > 0 && countdownSeconds === 0 &&
<span>Round underway...{roundSeconds}</span>
}
</div>
);
}
Or you could functionalize the rendering of the the timer, i.e.
renderTimer() {
const { countdownSeconds, roundSeconds } = this.state;
if(countdownSeconds > 0) {
return <span>Counting down...{countdownSeconds}</span>;
}
return <span>Round underway...{roundSeconds}</span>;
}
and then:
render() {
return (
<div>
{this.renderTimer()}
</div>
);
}
I have a piece of JS code that displays and array of data in the frontend. Looks like this:
var compliments = {
complimentLocation: '.compliment',
currentCompliment: '',
complimentList: {
'morning': mycompliments.morning,
'afternoon': mycompliments.afternoon,
'evening': mycompliments.evening
},
updateInterval: mycompliments.interval || 30000,
fadeInterval: mycompliments.fadeInterval || 4000,
intervalId: null
};
compliments.updateCompliment = function () {
var _list = [];
var hour = moment().hour();
if (hour >= 3 && hour < 12) {
_list = compliments.complimentList['morning'].slice();
} else if (hour >= 12 && hour < 17) {
_list = compliments.complimentList['afternoon'].slice();
} else if (hour >= 17 || hour < 3) {
_list = compliments.complimentList['evening'].slice();
} else {
Object.keys(compliments.complimentList).forEach(function (_curr) {
_list = _list.concat(compliments.complimentList[_curr]).slice();
});
}
var _spliceIndex = _list.indexOf(compliments.currentCompliment);
if (_spliceIndex !== -1) {
_list.splice(_spliceIndex, 1);
}
var _randomIndex = Math.floor(Math.random() * _list.length);
compliments.currentCompliment = _list[_randomIndex];
$('.compliment').updateWithText(compliments.currentCompliment, compliments.fadeInterval);
}
compliments.init = function () {
this.updateCompliment();
this.intervalId = setInterval(function () {
this.updateCompliment();
}.bind(this), this.updateInterval)
}
The last function gets triggered on page load and grabs the content of var compliments from an external js file.
What I'm trying to do is to include a timeout, so that the external file gets reloaded every X seconds, and the content displayed in the frontend replaced.
I've tried adding this timeout function at the end of the file (so it gets triggered on page load, and with the timeout afterwards) but so far no luck:
function functionToLoadFile(){
jQuery.get('js/mycompliments.js', function(data) {
compliments.init = function () {
this.updateCompliment();
this.intervalId = setInterval(function () {
this.updateCompliment();
}.bind(this), this.updateInterval)
}
setTimeout(functionToLoadFile, 1000);
});
}
What am I missing?
I have THIS timer in my project.
When it runs out, it shows a Time Up screen, which works fine.
But when the player is Game Over, i show the Game Over screen, but the timer keeps running and when it hits 00:00 then it switches to the Time Up screen.
How can i make this timer stop counting down and set to 00:00 again?
I tried adding a function like this:
CountDownTimer.prototype.stop = function() {
diff = 0;
this.running = false;
};
I also tried to change the innerHTML but its obvious that its just changing the numbers without stopping the timer and after a second it will show the count down again... I don't know what to call.
//Crazy Timer function start
function CountDownTimer(duration, granularity) {
this.duration = duration;
this.granularity = granularity || 1000;
this.tickFtns = [];
this.running = false;
}
CountDownTimer.prototype.start = function() {
if (this.running) {
return;
}
this.running = true;
var start = Date.now(),
that = this,
diff, obj;
(function timer() {
diff = that.duration - (((Date.now() - start) / 1000) | 0);
if (diff > 0) {
setTimeout(timer, that.granularity);
} else {
diff = 0;
that.running = false;
}
obj = CountDownTimer.parse(diff);
that.tickFtns.forEach(function(ftn) {
ftn.call(this, obj.minutes, obj.seconds);
}, that);
}());
};
CountDownTimer.prototype.onTick = function(ftn) {
if (typeof ftn === 'function') {
this.tickFtns.push(ftn);
}
return this;
};
CountDownTimer.prototype.expired = function() {
return !this.running;
};
CountDownTimer.parse = function(seconds) {
return {
'minutes': (seconds / 60) | 0,
'seconds': (seconds % 60) | 0
};
};
window.onload = function () {
var display = document.querySelector('#countDown'),
timer = new CountDownTimer(timerValue),
timeObj = CountDownTimer.parse(timerValue);
format(timeObj.minutes, timeObj.seconds);
timer.onTick(format).onTick(checkTime);
document.querySelector('#startBtn').addEventListener('click', function () {
timer.start();
});
function format(minutes, seconds) {
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.textContent = minutes + ':' + seconds;
}
function checkTime(){
if(this.expired()) {
timeUp();
document.querySelector('#startBtn').addEventListener('click', function () {
timer.start();
});
}
}
};
Instead of recursively calling setTimeout, try setInterval instead. You could then store a reference to the timer:
this.timer = setInterval(functionToRunAtInterval, this.granularity);
and kill it when the game finishes::
clearInterval(this.timer)
(see MDN's docs for more info on setInterval)
It's been a while and I'm not sure if you've figured it out but check out the fiddle below:
https://jsfiddle.net/f8rh3u85/1/
// until running is set to false the timer will keep running
if (that.running) {
if (diff > 0) {
setTimeout(timer, that.granularity);
} else {
diff = 0;
that.running = false;
}
obj = CountDownTimer.parse(diff);
that.tickFtns.forEach(function(ftn) {
ftn.call(this, obj.minutes, obj.seconds);
}, that);
}
I've added a button that causes running to be set to false which stops the timer.
Button:
<button id="stop">Game Over</button>
Code:
$( "#stop" ).click(function() {
timer.running = false;
});
So that should hopefully get you to where you need to be.
Similar to Tom Jenkins' answer, you need to cancel the next tick by avoiding the diff > 0 branch of your if statement. You could keep your code as it stands and use your suggested stop method, however, you'd need to change your logic around handling ticks to check that both running === true and a new param gameOver === false.
Ultimately, I think your problem is that no matter whether the timer is running or not, you'll always execute this code on a tick:
obj = CountDownTimer.parse(diff);
that.tickFtns.forEach(function(ftn) {
ftn.call(this, obj.minutes, obj.seconds);
}, that);
If you have a game over state, you probably don't want to call the provided callbacks, so add some conditional check in there.