I am making a timer using React & State only, there is Session time which is set to 2 minutes and Break time, set to 1 minute. When play button is clicked, the timer will start decrementing the value, and when the session time become 0, it will move to break time and when break time become 0, it will move to session time untill the stop button or reset is pressed.
Here I am facing two problems:
Please find my complete code from CodePen
handleTimer = () => {
this.interval = setInterval(()=>{
if(this.state.sessionMin == 0 && this.state.Sec == 0){
clearInterval(this.interval);
this.setState({
sessionMin: this.state.sessionConst
})
this.breakInterval = setInterval(()=>{
if(this.state.breakMin == 0 && this.state.Sec == 0){
clearInterval(this.breakInterval);
this.setState({
breakMin: this.state.breakConst
})
console.log(this.state.breakMin)
this.handleTimer()
}
this.setState({
display: 'Break',
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
})
},1000)
}
this.setState({
display: 'Session',
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
sessionMin: this.state.Sec == 0 ? this.state.sessionMin-1 : this.state.sessionMin
})
},1000)
}
Note: second is set to 10s, instead of 60s for quick test. Note: Reset button is not functioning yet. Note: when ever play button is pressed after stop button, it should continue from that state, not to start over again.
I use 'state' to get latest or most recent data, but it did not work. I don't want to try hooks or any other stuff, want to use only simple component level state.
If you want to skip explanation and go to solution, scroll down.
Let's say, your session timer reached the state Session: 0min 0sec. Look at the code below (with comments):
// current state: *Session: 0min 0sec*
this.interval = setInterval(()=>{
if (this.state.sessionMin == 0 && this.state.Sec == 0) {
// You tell a browser API "Stop calling this interval function in
// future", but execution of current function call continues !!!
clearInterval(this.interval)
// Execution continues, right? So, this.setState.sessionMin
// takes value of this.state.sessionConst, that is 2
this.setState({ sessionMin: this.state.sessionConst })
// Here you are setting breakInterval, but runtime doesn't care
// about it for now, because first call of this.breakInterval
// will be made after 1000ms
// (setInterval(() => {}, 1000)
// ...wait 1000ms ...
// first call
// ... wait 1000ns
// second call
// ... etc
this.breakInterval = setInterval(()=>{
// ...breakInterval logic
},1000)
}
this.setState({
display: 'Session',
// this.state.Sec == 0, so it's value is set to 10
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
// As mentioned above, this.state.sessionMin is 2,
// so here this.state.sessionMin is set to 1 (2 - 1)
sessionMin: this.state.Sec == 0 ? this.state.sessionMin-1 : this.state.sessionMin
})
},1000)
So after execution of code above state of your timer is Session: 1min 10sec, not Break: 0min 10sec as you expected. Then after 1s breakInterval is called, look at the code below:
// current state: *Session: 1min 10sec*
this.breakInterval = setInterval(()=>{
// No, condition is not satisfied, because current
// state is *Session: 1min 10sec*
if(this.state.breakMin == 0 && this.state.Sec == 0){
// ...clear breakInterval logic
}
this.setState({
// this.state.display will be 'Break'
display: 'Break',
// and this.state.Sec will be 9
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
// As mentioned above, current state is still **Session: 1min
// 10sec**, i.e. this.state.Sec is 10. So in
// ternary operator SECOND expression will be executed
// breakMin: this.state.breakMin
breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
})
},1000)
breakMin is initialized in constructor as 1
this.state={
display: 'Session',
sessionMin: 2,
sessionConst:2,
// Yep, here
breakMin: 1,
breakConst: 1,
Sec: 0,
}
Voila! After Session: 1min 10sec, state of your timer is Break: 1min 9sec now. There are several problems here:
Why after first cycle timer state Session: 1min 10sec changes to Break: 0min 9sec, i.e. number of minutes is correct? Frankly speaking, is not correct. So, after Break: 1min 9sec timer reached Break: 0min 0sec. Again, look at code below:
// current state: Break: 0min 0sec**
this.breakInterval = setInterval(()=>{
// Yes, condition is satisfied
if(this.state.breakMin == 0 && this.state.Sec == 0){
// You tell a browser API "Stop calling this function in
// future", but current function execution continues !!!
clearInterval(this.breakInterval)
// this.state.breakMin is now 1
this.setState({ breakMin: this.state.breakConst })
console.log(this.state.breakMin)
// this.handleTimer() is called, but this.interval()
// will be called after 1000ms
this.handleTimer()
}
this.setState({
// this.state.display will be 'Break'
display: 'Break',
// this.state.Sec will be 10
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
// this.state.Sec is still 0, so in ternary operator
// FIRST expression will be executed
// breakMin: this.state.breakMin - 1
// Because current value of this.state.breakMin is 1,
// result value will be 0 (1 - 1) !!!!!!!
breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
})
},1000)
After execution of code above, actual value of this.state.breakMin will be 0, not 1, and current state will be Break: 0min 10sec. After that, session interval will be called, state will be Session: 1min 9sec, the cycle will start again. Let's say session interval reached Session: 0min 0sec. As you remeber, after that timer state will be Session: 1min 10sec (see the first code example). So:
// **Session: 1min 10sec**
this.breakInterval = setInterval(()=>{
// No, condition is not satisfied
if(this.state.breakMin == 0 && this.state.Sec == 0){
// ...clear breakInterval logic
}
this.setState({
display: 'Break',
// this.state.Sec will be 9
Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
// this.state.Sec is 10, so in
// ternary operator SECOND expression will be executed
// breakMin: this.state.breakMin
// Beacause actual value of this.state.breakMin is 0,
// new breakMin value will be zero too
breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
})
},1000)
Therefore, after the first cycle the number of minutes is correct (although everything inside does not work quite as it should)
So, the solution:
class TypesOfFood extends React.Component {
constructor(props) {
super(props)
this.state={
display: 'Session',
paused: false,
sec: 0,
min: this.sessionMinutesConst,
}
}
// use 9, not 10 for tests or 59, not 60 in "production"
// it will be look like:
// 2:01 -> 2:00 -> 1:59
secondsInOneMinute = 9
sessionMinutesConst = 2
breakMinutesConst = 1
timer = null
decrementTimer = () => {
this.setState(currentState => ({
sec: currentState.sec === 0 ? this.secondsInOneMinute : currentState.sec - 1,
min: currentState.sec === 0 ? currentState.min - 1 : currentState.min
}))
}
createTimer = () => {
this.timer = setInterval(() => {
this.handleTimer()
this.decrementTimer()
}, 1000)
}
handleTimer = () => {
const { sec, min } = this.state
const isSessionTimer = this.state.display === 'Session'
if (sec === 0 && min === 0) {
this.setState({
display: isSessionTimer ? 'Break' : 'Session',
min: isSessionTimer ? this.breakMinutesConst : this.sessionMinutesConst
})
}
}
handleStop = () => {
this.setState({ paused: true })
clearInterval(this.timer)
}
handleReset = () => {
clearInterval(this.timer)
this.timer = null
this.setState({
display: 'Session',
paused: false,
sec: 0,
min: this.sessionMinutesConst,
})
}
handlePlay = () => {
if (this.timer && !this.state.paused) return
this.createTimer()
this.setState({ paused: false })
}
render() {
return (
<div id="container">
<h3>{this.state.display}</h3>
<h1>{this.state.min}m:{this.state.sec}s</h1>
<button onClick={this.handlePlay}>Play</button>
<button onClick={this.handleStop}>Stop</button>
<button onClick={this.handleReset}>Reset</button>
</div>
)
}
}
What you should pay attention to:
Hope that helped!