Home > Back-end >  Countdown Timer using functional component in ReactJS doesn't work properly
Countdown Timer using functional component in ReactJS doesn't work properly

Time:06-01

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 &#8679 */}
                <button class = 'countdown-setTime' onClick={() => setDuration("addHours")}>&#8679;</button>
                <button class = 'countdown-setTime' onClick={() => setDuration("addMinutes")}>&#8679;</button>
                <button class = 'countdown-setTime' onClick={() => setDuration("addSeconds")}>&#8679;</button>
            
                <div className="countdown-timeDisplay">
                    {hours} : {minutes} : {seconds}
                </div>
            
                {/* Downwards White Arrow &#8681 */}
                <button class = 'countdown-setTime' onClick={() => setDuration("minusHours")}>&#8681;</button>
                <button class = 'countdown-setTime' onClick={() => setDuration("minusMinutes")}>&#8681;</button>
                <button class = 'countdown-setTime' onClick={() => setDuration("minusSeconds")}>&#8681;</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 &#8679 */}
                <button className= 'countdown-setTime' onClick={() => setDuration("addHours")}>&#8679;</button>
                <button className= 'countdown-setTime' onClick={() => setDuration("addMinutes")}>&#8679;</button>
                <button className= 'countdown-setTime' onClick={() => setDuration("addSeconds")}>&#8679;</button>
            
                <div className="countdown-timeDisplay">
                    {hours} : {minutes} : {seconds}
                </div>
            
                {/* Downwards White Arrow &#8681 */}
                <button className= 'countdown-setTime' onClick={() => setDuration("minusHours")}>&#8681;</button>
                <button className= 'countdown-setTime' onClick={() => setDuration("minusMinutes")}>&#8681;</button>
                <button className= 'countdown-setTime' onClick={() => setDuration("minusSeconds")}>&#8681;</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;
  • Related