I want to make a countdown timer with start, stop, resume and reset buttons. I have tried to use class components to do that and it works fine. However, when I tried to convert that into a functional component, my code doesn't seem to work. I could not figure out why even after 1 day, and I am guessing that the issue lies in the part on timer, setInterval and clearInterval.
I have attached my JavaScript code below.
Help would be greatly appreciated, thanks!
import React, { useState } from "react";
function Countdown() {
const [ timerRunning, setTimerRunning ] = useState(false); // Is the timer running?
const [ startTime, setStartTime ] = useState(0); // Start time (in milliseconds).
const [ totalTime, setTotalTime ] = useState(0); // Total time (in ms) the timer will run.
var timer;
// Called when timer is started or resumed.
// Start the timer by setting it to running state, and setting current time as startTime and totalTime.
const startTimer = () => {
setTimerRunning(true);
setStartTime(totalTime);
setTotalTime(totalTime);
timer = setInterval(() => {
const remainingTime = totalTime - 1000;
if (remainingTime >= 0) {
// Update total time accordingly.
setTotalTime(remainingTime);
} else {
// Remaining time < 0,
// clear the interval, stop timer from running, alert user with a message.
clearInterval(timer);
setTimerRunning(false);
setStartTime(0);
setTotalTime(0);
alert("Congratulations! You have successfully completed your study session!");
}
}, 1000);
};
// Called when timer is stopped.
// Stop the timer by clearing the interval and stopping the timer from running.
const stopTimer = () => {
clearInterval(timer);
setTimerRunning(false);
};
// Called when timer is reset.
const resetTimer = () => {
if (timerRunning === false) {
// If timer has stopped running,
// reset totalTime to startTime.
setTotalTime(0);
setStartTime(0);
}
};
const setDuration = input => {
const max = 86400000; // 24 hours.
if (!timerRunning) {
if (input === "addHours" && totalTime 3600000 < max) {
setTotalTime(totalTime 3600000);
} else if (input === "minusHours" && totalTime - 3600000 >= 0) {
setTotalTime(totalTime - 3600000);
} else if (input === "addMinutes" && totalTime 60000 < max) {
setTotalTime(totalTime 60000);
} else if (input === "minusMinutes" && totalTime - 60000 >= 0) {
setTotalTime(totalTime - 60000);
} else if (input === "addSeconds" && totalTime 1000 < max) {
setTotalTime(totalTime 1000);
} else if (input === "minusSeconds" && totalTime - 1000 >= 0) {
setTotalTime(totalTime - 1000);
}
}
};
let seconds = ("0" (Math.floor((totalTime / 1000) % 60) % 60)).slice(-2);
let minutes = ("0" Math.floor((totalTime / 60000) % 60)).slice(-2);
let hours = ("0" Math.floor((totalTime / 3600000) % 60)).slice(-2);
return (
<div className="countdown">
<div className="countdown-title">Countdown</div>
<div className="countdown-labels">Hours : Minutes : Seconds</div>
<div className="countdown-buttons">
{/* Upwards White Arrow ⇧ */}
<button class = 'countdown-setTime' onClick={() => setDuration("addHours")}>⇧</button>
<button class = 'countdown-setTime' onClick={() => setDuration("addMinutes")}>⇧</button>
<button class = 'countdown-setTime' onClick={() => setDuration("addSeconds")}>⇧</button>
<div className="countdown-timeDisplay">
{hours} : {minutes} : {seconds}
</div>
{/* Downwards White Arrow ⇩ */}
<button class = 'countdown-setTime' onClick={() => setDuration("minusHours")}>⇩</button>
<button class = 'countdown-setTime' onClick={() => setDuration("minusMinutes")}>⇩</button>
<button class = 'countdown-setTime' onClick={() => setDuration("minusSeconds")}>⇩</button>
</div>
<div >
{timerRunning === false && (startTime === 0 || startTime === totalTime || totalTime === 0) && (
<button className="countdown-start" onClick={startTimer}>
Start
</button>
)}
{timerRunning === true && totalTime >= 1000 && (
<button className="countdown-stop" onClick={stopTimer}>
Stop
</button>
)}
{timerRunning === false && (startTime > 0 && startTime !== totalTime && totalTime !== 0) && (
<button className="countdown-start" onClick={startTimer}>
Resume
</button>
)}
{timerRunning === false && (startTime > 0 || startTime !== totalTime || totalTime > 0) && (
<button className="countdown-reset" onClick={resetTimer}>
Reset
</button>
)}
</div>
</div>
);
}
export default Countdown;
CodePudding user response:
you have to put your setInterval
in React.useEffect
take a look at this post
some suggestions for your code:
- use className instead of class attribute in your elements.
- don't use var or let in react functional components.
CodePudding user response:
useState doesn't change value immediately. So it's value is not actually changing. To avoid this use UseEffect
hook.
useEffect(() => {
let intervalid
// check if timer is running, if yes start interval
if (timerRunning){
intervalid = setInterval(() => {
const remainingTime = totalTime - 1000;
if (remainingTime >= 0) {
setTotalTime(remainingTime);
} else {
clearInterval(intervalid);
setTimerRunning(false);
setStartTime(0);
setTotalTime(0);
alert("Congratulations! You have successfully completed your study session!");
}
}, 1000);
}
return () => {
// To stop the timer
clearInterval(intervalid)
}
}, [timerRunning,totalTime]) // each time totalTime or timerRunning changes useEffect is called
Here is the full code
import React, { useEffect, useState } from "react";
function Countdown() {
const [ timerRunning, setTimerRunning ] = useState(false); // Is the timer running?
const [ startTime, setStartTime ] = useState(0); // Start time (in milliseconds).
const [ totalTime, setTotalTime ] = useState(0); // Total time (in ms) the timer will run.
useEffect(() => {
let intervalid
// check if timer is running, if yes start interval
if (timerRunning){
intervalid = setInterval(() => {
const remainingTime = totalTime - 1000;
if (remainingTime >= 0) {
setTotalTime(remainingTime);
} else {
clearInterval(intervalid);
setTimerRunning(false);
setStartTime(0);
setTotalTime(0);
alert("Congratulations! You have successfully completed your study session!");
}
}, 1000);
}
return () => {
// To stop the timer
clearInterval(intervalid)
}
}, [timerRunning,totalTime]) // each time totalTime or timerRunning changes useEffect is calle
// Called when timer is started or resumed.
// Start the timer by setting it to running state, and setting current time as startTime and totalTime.
const startTimer = () => {
setTimerRunning(true);
};
// Called when timer is stopped.
// Stop the timer by clearing the interval and stopping the timer from running.
const stopTimer = () => {
setTimerRunning(false);
};
// Called when timer is reset.
const resetTimer = () => {
if (timerRunning === false) {
// If timer has stopped running,
// reset totalTime to startTime.
setTotalTime(0);
setStartTime(0);
}
};
const setDuration = input => {
const max = 86400000; // 24 hours.
if (!timerRunning) {
if (input === "addHours" && totalTime 3600000 < max) {
setTotalTime(totalTime 3600000);
} else if (input === "minusHours" && totalTime - 3600000 >= 0) {
setTotalTime(totalTime - 3600000);
} else if (input === "addMinutes" && totalTime 60000 < max) {
setTotalTime(totalTime 60000);
} else if (input === "minusMinutes" && totalTime - 60000 >= 0) {
setTotalTime(totalTime - 60000);
} else if (input === "addSeconds" && totalTime 1000 < max) {
setTotalTime(totalTime 1000);
} else if (input === "minusSeconds" && totalTime - 1000 >= 0) {
setTotalTime(totalTime - 1000);
}
}
};
let seconds = ("0" (Math.floor((totalTime / 1000) % 60) % 60)).slice(-2);
let minutes = ("0" Math.floor((totalTime / 60000) % 60)).slice(-2);
let hours = ("0" Math.floor((totalTime / 3600000) % 60)).slice(-2);
return (
<div className="countdown">
<div className="countdown-title">Countdown</div>
<div className="countdown-labels">Hours : Minutes : Seconds</div>
<div className="countdown-buttons">
{/* Upwards White Arrow ⇧ */}
<button className= 'countdown-setTime' onClick={() => setDuration("addHours")}>⇧</button>
<button className= 'countdown-setTime' onClick={() => setDuration("addMinutes")}>⇧</button>
<button className= 'countdown-setTime' onClick={() => setDuration("addSeconds")}>⇧</button>
<div className="countdown-timeDisplay">
{hours} : {minutes} : {seconds}
</div>
{/* Downwards White Arrow ⇩ */}
<button className= 'countdown-setTime' onClick={() => setDuration("minusHours")}>⇩</button>
<button className= 'countdown-setTime' onClick={() => setDuration("minusMinutes")}>⇩</button>
<button className= 'countdown-setTime' onClick={() => setDuration("minusSeconds")}>⇩</button>
</div>
<div className="button-wrapper">
{timerRunning === false && (startTime === 0 || startTime === totalTime || totalTime === 0) && (
<button className="countdown-start" onClick={startTimer}>
Start
</button>
)}
{timerRunning === true && totalTime >= 1000 && (
<button className="countdown-stop" onClick={stopTimer}>
Stop
</button>
)}
{timerRunning === false && (startTime > 0 && startTime !== totalTime && totalTime !== 0) && (
<button className="countdown-start" onClick={startTimer}>
Resume
</button>
)}
{timerRunning === false && (startTime > 0 || startTime !== totalTime || totalTime > 0) && (
<button className="countdown-reset" onClick={resetTimer}>
Reset
</button>
)}
</div>
</div>
);
}
export default Countdown;