The following is the code breakdown leading to the part where I run into trouble.
I defined a class component to store the state data and the "timerState" , which is the main focus in this case, will be toggled between true and false
this.state={
brkLength:1,
sesnLength:1,
timer:60,
timerState:'false',
timerType:'Session',
}
The handleTimer function will be fired up once the onclick event takes place. Since setstate runs asynchronously and I don't want the function-timeCountDown and breakCountDown to be called before state mutation, I set them as callback function of setState.
handleTimer(){
console.log(this.state.timerState)
if(this.state.timerType=="Session"){
this.setState({
timerState:!this.state.timerState
},()=>this.timeCountDown())
}else if(this.state.timerType=="Break"){
this.setState({
timerState:!this.state.timerState
},()=>this.breakCountDown())
}
}
However, as the console.log show in two places-one in handleTimer and the other in timeCountDown, both of them print "false".
timeCountDown(){
console.log(this.state.timerState)
if(this.state.timerState){
this.myCountDown=setInterval(()=>{
if(this.state.timer>0){
this.setState(prevState=>({
timer:prevState.timer-1
}))
}else if(this.state.timer<=0){
clearInterval(this.myCountDown)
this.soundPlay()
this.setState({
timerType:'Break',
timer:this.state.brkLength*60,
},()=>this.breakCountDown())
}
}
,1000)
}else{
clearInterval(this.myCountDown)
}
}
I wonder what goes wrong above in the code snippet. Here comes the link to see the entire coding if you want to look into it.
function formatTime(time){
let minutes=Math.floor(time/60)
let seconds=time%60
minutes=minutes<10?"0"+minutes:minutes
seconds=seconds<10?"0"+seconds:seconds
return minutes +":"+seconds
}
const TimerLengthControl=(props)=>(
<div className="LengthContainer">
<div className="controlTitle" id={props.titleID}>{props.title}</div>
<div>
<button
id={props.decrementID}
value="-1"
type={props.type}
onClick={props.onClick}
>
<i className="fas fa-arrow-down"></i>
</button>
<span id={props.spanID}>{props.span}</span>
<button
id={props.incrementID}
value="+1"
type={props.type}
onClick={props.onClick}
>
<i className="fas fa-arrow-up"></i>
</button>
</div>
</div>
)
const TimerControl=(props)=>(
<div className="timerControlContainer">
<div className="timerContainer">
<div id="timer-label">{props.timerType}</div>
<div id="time-left">{formatTime(props.timeLeft)}</div>
</div>
<div className="buttonContainer">
<button id="start_stop" onClick={props.timerHandler}>
<i className="fas fa-play"/>
<i className="fas fa-pause"/>
</button>
<button id="reset">
<i className="fas fa-sync" onClick={props.resetHandler}/>
</button>
</div>
</div>
)
class App extends React.Component{
constructor(){
super()
this.state={
brkLength:1,
sesnLength:1,
timer:60,
timerState:'false',
timerType:'Session',
}
this.handleReset=this.handleReset.bind(this)
this.handleOperation=this.handleOperation.bind(this)
this.handleBreakLength=this.handleBreakLength.bind(this)
this.handleSessionLength=this.handleSessionLength.bind(this)
this.handleTimer=this.handleTimer.bind(this)
};
handleReset(){
clearInterval(this.myCountDown)
this.setState({
brkLength:5,
sesnLength:25,
timer:1500,
timerState:'false',
timerType:'Session',
})
}
timeCountDown(){
console.log(this.state.timerState)
if(this.state.timerState){
this.myCountDown=setInterval(()=>{
if(this.state.timer>0){
this.setState(prevState=>({
timer:prevState.timer-1
}))
}else if(this.state.timer<=0){
clearInterval(this.myCountDown)
this.soundPlay()
this.setState({
timerType:'Break',
timer:this.state.brkLength*60,
},()=>this.breakCountDown())
}
}
,1000)
}else{
clearInterval(this.myCountDown)
}
}
soundPlay(){
const audio= new Audio("https://raw.githubusercontent.com/freeCodeCamp/cdn/master/build/testable-projects-fcc/audio/BeepSound.wav")
audio.play()
}
breakCountDown(){
if(this.state.timerState){
this.myCountDown=setInterval(()=>{
if(this.state.timer>0){
this.setState({timer:this.state.timer-1})
}else if(this.state.timer<=0){
clearInterval(this.myCountDown)
this.soundPlay()
this.setState({
timerType:'Session',
timer:this.state.sesnLength*60
})
}
},1000)
}else{
clearInterval(this.myCountDown)
}
}
handleTimer(){
console.log(this.state.timerState)
if(this.state.timerType=="Session"){
this.setState({
timerState:!this.state.timerState
},()=>this.timeCountDown())
}else if(this.state.timerType=="Break"){
this.setState({
timerState:!this.state.timerState
},()=>this.breakCountDown())
}
}
handleOperation(stateToChange,amount){
const breakLength=this.state.brkLength
const sessionLength=this.state.sesnLength
if(stateToChange=="sesnLength"&&sessionLength==1&&amount<0){
return
}else if(stateToChange=="sesnLength"&&sessionLength==60&&amount>0){
return
}else if(stateToChange=="sesnLength"){
this.setState({
[stateToChange]:this.state[stateToChange]+Number(amount)*1,
timer:this.state.timer+Number(amount)*60
})
}
if(stateToChange=="brkLength"&&breakLength==1&&amount<0){
return
}else if (stateToChange=="brkLength"){
this.setState({[stateToChange]:this.state[stateToChange]+Number(amount)})
}
}
handleBreakLength(e){
const {value}=e.currentTarget
const type="brkLength"
this.handleOperation(type,value)
}
handleSessionLength(e){
const {value}=e.currentTarget
const type="sesnLength"
this.handleOperation(type,value)
}
render(){
return(
<div>
<TimerLengthControl
title="Break Length"
titleID="break-label"
decrementID="break-decrement"
incrementID="break-increment"
spanID="break-length"
span={this.state.brkLength}
onClick={this.handleBreakLength}
/>
<TimerLengthControl
title="Session Length"
titleID="session-label"
decrementID="session-decrement"
incrementID="session-increment"
spanID="session-length"
span={this.state.sesnLength}
onClick={this.handleSessionLength}
/>
<TimerControl
timeLeft={this.state.timer}
resetHandler={this.handleReset}
timerHandler={this.handleTimer}
timerType={this.state.timerType}
/>
</div>
);
}
}
ReactDOM.render(<App />,document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
</head>
<body>
<div id="root">
</div>
</body>
</html>
The problem is that you've used 'false'
instead of false
for the "off" value for timerState
:
this.setState({
brkLength: 5,
sesnLength: 25,
timer: 1500,
timerState: 'false', // <==== here
timerType: 'Session',
});
That's a string, not a boolean. And since it's a non-blank string, it's truthy. So
if (this.state.timerState) {
...branches into the if
block when timerState
is 'false'
Later, when you do
this.setState({
timerState: !this.state.timerState
}, () => this.timeCountDown());
...it changes 'false'
(a string) to false
(a boolean).
Booleans don't go in quotes:
this.setState({
brkLength: 5,
sesnLength: 25,
timer: 1500,
timerState: false, // <====
timerType: 'Session',
});
Updated:
function formatTime(time) {
let minutes = Math.floor(time / 60);
let seconds = time % 60;
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
return minutes + ":" + seconds;
}
const TimerLengthControl = (props) => (
<div className="LengthContainer">
<div className="controlTitle" id={props.titleID}>{props.title}</div>
<div>
<button
id={props.decrementID}
value="-1"
type={props.type}
onClick={props.onClick}
>
<i className="fas fa-arrow-down"></i>
</button>
<span id={props.spanID}>{props.span}</span>
<button
id={props.incrementID}
value="+1"
type={props.type}
onClick={props.onClick}
>
<i className="fas fa-arrow-up"></i>
</button>
</div>
</div>
);
const TimerControl = (props) => (
<div className="timerControlContainer">
<div className="timerContainer">
<div id="timer-label">{props.timerType}</div>
<div id="time-left">{formatTime(props.timeLeft)}</div>
</div>
<div className="buttonContainer">
<button id="start_stop" onClick={props.timerHandler}>
<i className="fas fa-play" />
<i className="fas fa-pause" />
</button>
<button id="reset">
<i className="fas fa-sync" onClick={props.resetHandler} />
</button>
</div>
</div>
);
class App extends React.Component {
constructor() {
super();
this.state = {
brkLength: 1,
sesnLength: 1,
timer: 60,
timerState: false, // <==== here
timerType: 'Session',
};
this.handleReset = this.handleReset.bind(this);
this.handleOperation = this.handleOperation.bind(this);
this.handleBreakLength = this.handleBreakLength.bind(this);
this.handleSessionLength = this.handleSessionLength.bind(this);
this.handleTimer = this.handleTimer.bind(this);
};
handleReset() {
clearInterval(this.myCountDown);
this.setState({
brkLength: 5,
sesnLength: 25,
timer: 1500,
timerState: false, // <==== here
timerType: 'Session',
});
}
timeCountDown() {
console.log(1, this.state.timerState);
if (this.state.timerState) {
this.myCountDown = setInterval(() => {
if (this.state.timer > 0) {
this.setState(prevState => ({
timer: prevState.timer - 1
}));
} else if (this.state.timer <= 0) {
clearInterval(this.myCountDown);
this.soundPlay();
this.setState({
timerType: 'Break',
timer: this.state.brkLength * 60,
}, () => this.breakCountDown());
}
}
, 1000);
} else {
clearInterval(this.myCountDown);
}
}
soundPlay() {
const audio = new Audio("https://raw.githubusercontent.com/freeCodeCamp/cdn/master/build/testable-projects-fcc/audio/BeepSound.wav");
audio.play();
}
breakCountDown() {
if (this.state.timerState) {
this.myCountDown = setInterval(() => {
if (this.state.timer > 0) {
this.setState({ timer: this.state.timer - 1 });
} else if (this.state.timer <= 0) {
clearInterval(this.myCountDown);
this.soundPlay();
this.setState({
timerType: 'Session',
timer: this.state.sesnLength * 60
});
}
}, 1000);
} else {
clearInterval(this.myCountDown);
}
}
handleTimer() {
console.log(2, this.state.timerState);
if (this.state.timerType == "Session") {
this.setState({
timerState: !this.state.timerState
}, () => this.timeCountDown());
} else if (this.state.timerType == "Break") {
this.setState({
timerState: !this.state.timerState
}, () => this.breakCountDown());
}
}
handleOperation(stateToChange, amount) {
const breakLength = this.state.brkLength;
const sessionLength = this.state.sesnLength;
if (stateToChange == "sesnLength" && sessionLength == 1 && amount < 0) {
return;
} else if (stateToChange == "sesnLength" && sessionLength == 60 && amount > 0) {
return;
} else if (stateToChange == "sesnLength") {
this.setState({
[stateToChange]: this.state[stateToChange] + Number(amount) * 1,
timer: this.state.timer + Number(amount) * 60
});
}
if (stateToChange == "brkLength" && breakLength == 1 && amount < 0) {
return;
} else if (stateToChange == "brkLength") {
this.setState({ [stateToChange]: this.state[stateToChange] + Number(amount) });
}
}
handleBreakLength(e) {
const { value } = e.currentTarget;
const type = "brkLength";
this.handleOperation(type, value);
}
handleSessionLength(e) {
const { value } = e.currentTarget;
const type = "sesnLength";
this.handleOperation(type, value);
}
render() {
return (
<div>
<TimerLengthControl
title="Break Length"
titleID="break-label"
decrementID="break-decrement"
incrementID="break-increment"
spanID="break-length"
span={this.state.brkLength}
onClick={this.handleBreakLength}
/>
<TimerLengthControl
title="Session Length"
titleID="session-label"
decrementID="session-decrement"
incrementID="session-increment"
spanID="session-length"
span={this.state.sesnLength}
onClick={this.handleSessionLength}
/>
<TimerControl
timeLeft={this.state.timer}
resetHandler={this.handleReset}
timerHandler={this.handleTimer}
timerType={this.state.timerType}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
</head>
<body>
<div id="root">
</div>
</body>
</html>
Also, as I said in my comment, when updating state based on existing state, in general you want to use the callback form. So instead of:
this.setState({
timerState: !this.state.timerState
}, () => this.timeCountDown());
do
this.setState(
({timerState}) => ({timerState: !timerState}),
() => this.timeCountDown()
);