Search code examples
reactjsmomentjscountdown

React JS - Countdown timer using Moment.js


I'm very new to React - so bear with my noob question. But I have a component I'm trying to display inside a button. I've tested the function in a fiddle and it works great, but for some reason this module is showing up blank when I test it.

(using moment.js)

Here's my files:

//countdown.js
import moment from 'moment';

const CountDownTimer = () => {
    var eventTime = '1626573600';
    var currentTime = (Math.floor(Date.now() / 1000)).toString();
    var leftTime = eventTime - currentTime;
    var duration = moment.duration(leftTime, 'seconds');
    var interval = 1000;

    function ShowTimer() {
        setInterval(function(){
            // Time Out check
            if (duration.asSeconds() <= 0) {
                clearInterval(interval);
                //window.location.reload(true); //#skip the cache and reload the page from the server
            }

            //Otherwise
            duration = moment.duration(duration.asSeconds() - 1, 'seconds');
            return (duration.days() + ' Days ' + duration.hours()+ ' Hours ' + duration.minutes()+ ' Minutes ' + duration.seconds() + ' Seconds');
        }, interval);
    }

    return (
        <span>{ShowTimer()}</span>
    )
}
export default CountDownTimer;

Here's the usage:

//Slider.jsx
import React, { useState } from "react";
import CountDownTimer from "../../scripts/countdown.js";

...
...
...


   <div className="div-buyNowBtn">            
      <button id="buyNowBtn" className="white-fill-bg btn-outline btn-lg">
         Sale Active in: <CountDownTimer/>
      </button>
   </div>
            


Solution

  • The issue

    The issue with the original code is that ShowTimer was returning nothing, essentially meaning your return value was "void"

    The solution

    I decided to clean up the function a bit and make it more performant. Instead of having unnecessary renders occur due to un-tracked internal functions (ShowTimer) I decided to extract the logic outside of the component that stayed constant (calculateDuration).

    Then, using useEffect, I started my timer and made sure to update a new state variable called duration.

    Finally, to ensure we properly update, and kill the timer whenever the component unmounts / or the event time changes, I make sure to clearInterval. This should prevent any memory leaks from occurring.

    All wrapped up with a nice bow:

    const { useCallback, useEffect, useRef, useState } = React;
    
    const calculateDuration = eventTime => moment.duration(Math.max(eventTime - (Math.floor(Date.now() / 1000)), 0), 'seconds');
    
    function Countdown({ eventTime, interval }) {
      const [duration, setDuration] = useState(calculateDuration(eventTime));
      const timerRef = useRef(0);
      const timerCallback = useCallback(() => {
        setDuration(calculateDuration(eventTime));
      }, [eventTime])
    
      useEffect(() => {
        timerRef.current = setInterval(timerCallback, interval);
    
        return () => {
          clearInterval(timerRef.current);
        }
      }, [eventTime]);
    
      return (
        <div>
          {duration.days()} Days {duration.hours()} Hours {duration.minutes()} Minutes {duration.seconds()} Seconds
        </div>
      )
    }
    
    
    ReactDOM.render(
      <Countdown eventTime={1626573600} interval={1000} />,
      document.getElementById('app')
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
    <div id="app"></div>

    Hope this helped!