How to stop a second React timer from looping back from zero? - javascript

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>
);
}

Related

How to update variable passed into arrow function after component being rendered

I'm trying to build pomodoro clock (25+5).
I have an arrow function in the child component which takes two variabels: session (passed from parent, in seconds) and Boolean var.
Problem is the function always stays at 1500 seconds even after i modified session in the parent component.
It still passing the updated variable in the child but function always shows 25mins, cant find what am i doing wrong here.
The countdown itself works fine, i just need to figure out how to change amount of time passed in that function (called Countdown in the child component)
Child component:
import React, {useState, useEffect} from "react";
const Countdown = (Session, isStarted) => { // In this function Session is always 25 mins even after if i modify the state of the variable in the parent
let InitialMinutes = Math.floor(Session / 60);
let InitialSeconds = Session % 60;
const [minutes, setMinutes ] = useState(InitialMinutes);
const [seconds, setSeconds ] = useState(InitialSeconds);
useEffect(()=>{
if (isStarted) {
let myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
if (seconds === 0) {
if (minutes === 0) {
clearInterval(myInterval)
} else {
setMinutes(minutes - 1);
setSeconds(59);
}
}
}, 1000)
return ()=> {
clearInterval(myInterval);
};
}}, [minutes, seconds, isStarted]);
return (
<div>
{ minutes === 0 && seconds === 0
? null
: <h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds}</h1>
}
</div>
)
}
function Timer({session}) {
const [isStarted, setIsStarted] = React.useState(false);
return (
<div className='timer'>
<h3>Session</h3>
{Countdown(session, isStarted)}
<button onClick={() => setIsStarted(true)}>start</button>
<button>stop</button>
<button>{session}</button>
</div>
)
}
export default Timer
Parent component:
function App() {
const [Break, setBreak] = React.useState(5);
const [Session, setSession] = React.useState(1500);
const increaseBreak = () => {
setBreak(prevBreak => prevBreak + 1);
};
const decreaseBreak = () => {
if (Break === 5) return Break
setBreak(prevBreak => prevBreak - 1);
};
const increaseSession = () => {
setSession(prevSession => prevSession + 60);
console.log(Session)
}
const decreaseSession = () => {
return Session === 300? Session : setSession(prevSession => prevSession - 60);
}
const formatTime = time => {
let minutes = Math.floor(time / 60);
let seconds = time % 60;
return (minutes < 10? `0${minutes}` : minutes) + ':' + (seconds < 10? `0${seconds}` : seconds)
}
return (
<div className="App">
<div className="wrapper">
<div className='container'>
<h1>25 + 5 Clock</h1>
<div className='length'>
<div className='break-length'>
<h3>Break length</h3>
<div className='adjust'>
<button onClick={decreaseBreak}>-</button>
<div className='number'>{Break}</div>
<button onClick={increaseBreak} >+</button>
</div>
</div>
<div className='session-length'>
<h3>Session length</h3>
<div className="adjust">
<button onClick={decreaseSession}>-</button>
<div className='number' id='session'>{formatTime(Session)}</div>
<button onClick={increaseSession}>+</button>
</div>
</div>
</div>
<Timer session={Session}/>
</div>
</div>
</div>
);
}
export default App;
The Countdown Component never watched for changes in the session state. I Added a use effect at the top of it and changed your variables to states. Code Below.
const Countdown = (Session, isStarted) => {
console.log(Session);
// In this function Session is always 25 mins even after if i modify the state of the variable in the parent
const [InitialMinutes, setInialMinutes] = useState(Session / 60);
const [InitialSeconds, setInitialSeconds] = useState(Session % 60);
const [minutes, setMinutes] = useState(InitialMinutes);
const [seconds, setSeconds] = useState(InitialSeconds);
useEffect(() => {
setInitialSeconds(Session % 60);
setInialMinutes(Session / 60);
setMinutes(InitialMinutes);
setSeconds(InitialSeconds);
console.log(minutes);
}, [Session]);
useEffect(() => {
if (isStarted) {
let myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
if (seconds === 0) {
if (minutes === 0) {
clearInterval(myInterval);
} else {
setMinutes(minutes - 1);
setSeconds(59);
}
}
}, 1000);
return () => {
clearInterval(myInterval);
};
}
}, [minutes, seconds, isStarted]);
return (
<div>
{minutes === 0 && seconds === 0 ? null : (
<h1>
{" "}
{minutes}:{seconds < 10 ? `0${seconds}` : seconds}
</h1>
)}
</div>
);
};
function Timer({ session }) {
const [isStarted, setIsStarted] = React.useState(false);
return (
<div className="timer">
<h3>Session</h3>
{Countdown(session, isStarted)}
<button onClick={() => setIsStarted(true)}>start</button>
<button>stop</button>
<button>{session}</button>
</div>
);
}
export default Timer;

How i can listen/change my state with setIntreval?

I need show my timer in react component, he working in function but i can't show his in html because my state very bad working.
const date = { // timer context
hours: 0,
minutes: 0,
seconds: 0,
isActive: false,
start: function() {
this.isActive = true;
return this
},
stop: function() {
this.isActive = false;
return this
}
}
function timer() { //timer function
if(this.isActive) {
if(this.seconds < 60) date.seconds += 1;
else {
this.seconds = 0;
if(this.minutes < 60) date.minutes += 1;
else {
this.minutes = 0;
this.hours += 1;
}
}
}
return this
}
const myTimer = timer.bind(date);
const [timerState, setTimerState] = useState({}); // myState
let interval;
useEffect(() => {
if(interval) clearInterval(interval);
interval = setInterval(() => {
if(clients.length > 1) {
setTimerState(myTimer().start()); // state changed
} else {
setTimerState(myTimer().stop());
}
console.log(timerState);
}, 1000);
}, [clients, timerState /* waiting update states */]); // but i can't wait only clients otherwise i get empty object {} if remove timerState from wait states
I need withdraw timer state in html component return ({timerState})
Tell me please what i need. Thanks.

function clearInteraval not working in react [duplicate]

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);
}

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

React count down error

I have implemented a vanilla js countdown into a react component as follow:
import React, { Component } from 'react';
class CustomCountDown extends Component {
constructor(props) {
super(props);
this.endTime;
this.msLeft;
this.time;
this.hours;
this.mins;
this.element;
}
twoDigits( n ){
return (n <= 9 ? "0" + n : n);
}
updateTimer() {
this.msLeft = this.endTime - (+new Date);
if (this.msLeft < 1000 ) {
element.innerHTML = "countdown's over!";
} else {
this.time = new Date(this.msLeft );
this.hours = this.time.getUTCHours();
this.mins = this.time.getUTCMinutes();
this.element.innerHTML = (this.hours ? this.hours + ':' + this.twoDigits( this.mins ) : this.mins) + ':' + this.twoDigits( this.time.getUTCSeconds() );
setTimeout( this.updateTimer, this.time.getUTCMilliseconds() + 500 );
}
}
countdown( elementName, minutes, seconds ) {
this.element = document.getElementById( elementName );
this.endTime = (+new Date) + 1000 * (60*minutes + seconds) + 500;
this.updateTimer();
}
componentDidMount() {
this.countdown("count", 1, 30);
}
render() {
return(
<div id="count">
</div>
);
}
}
export default CustomCountDown;
I can't figure out why I am getting the following error:
When you pass this.updateTimer to setTimeout you loose context, i.e. this no longer points to your component instance. You need to keep the context either way:
setTimeout( this.updateTimer.bind(this), this.time.getUTCMilliseconds() + 500 );
setTimeout( () => this.updateTimer(), this.time.getUTCMilliseconds() + 500 );
As a better alternative, you can bind updateTimer in the constructor. This won't create new function every time updateTimer is called:
constructor(props) {
// ...
this.updateTimer = this.updateTimer.bind(this);
}

Categories

Resources