Count down timer in React

Count down timer in React

Learn how to create a count-down timer in React.

A Count-down timer is a multiple-purpose component that can display the count-down time from seconds to day or year left based on the configuration.

These types of components are often used on eCommerce websites to showcase the sales begin or end time, on OTT platforms to show the new series premier time, or even on an OTP verification page to show the OTP timeout.

The trickiest part of creating this component is understanding the component lifecycle and efficiently using the useEffect hook.

By reading the problem statement we think that using the setInterval timer function should do the work, well it does work but we don’t really need it.

Let’s say our component will receive the duration in milliseconds and it will keep on reducing the count by a second. What we can do is, store this duration in a state and inside the useEffect we can run a setTimeout timer that will be executed after a second on the mount and every update of the component.

Inside the setTimeout we can reduce the duration by 1000 (1 second) and update the state. This will trigger the component update (before the component is about to update clear the timer) and the useEffect hook will be invoked again, which will again run the setTimeout, and that will reduce the duration and update the state and so on.

This will keep running until the duration is not zero, once it’s zero we will clear the timer, and this stops the further updates. On the component unmount we will do the same to avoid the memory leaks.

  const [currentTime, setCurrentTime] = useState(expireIn);

  useEffect(() => {
    const timerId = setTimeout(() => {
      setCurrentTime(currentTime - 1000);
    }, 1000);

    if (currentTime <= 0) {
      onComplete && onComplete();
      clearTimeout(timerId);
    }

    return () => {
      clearTimeout(timerId);
    };
  }, [currentTime, onComplete]);

The final part is to format the duration which is in milliseconds to the understandable numerals and the same can be done by using simple calculations.

We will be following this formula.

// values in milliseconds
const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;

// convert milliseconds.
const day = Math.floor(time / DAY);
const hour = Math.floor((time % DAY) / HOUR);
const minutes = Math.floor((time % HOUR) / MINUTE);
const seconds = Math.floor((time % MINUTE) / SECOND);

Now we can pass this formula inside a function and return the JSX to render the value in numerals.

 const getFormattedTime = (time) => {
    const day = Math.floor(time / DAY);
    const hour = Math.floor((time % DAY) / HOUR);
    const minutes = Math.floor((time % HOUR) / MINUTE);
    const seconds = Math.floor((time % MINUTE) / SECOND);

    return (
      <Text>
        {day < 10 ? `0${day}` : day} : {hour < 10 ? `0${hour}` : hour} :{" "}
        {minutes < 10 ? `0${minutes}` : minutes} :{" "}
        {seconds < 10 ? `0${seconds}` : seconds}
      </Text>
    );
  };

Return this function from the component and count down timer should work as expected.

Complete code for count down timer

// timer.js
import React, { useEffect, useState } from "react";
import { Text } from "./styles";

const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;

const Timer = ({ expireIn, onComplete }) => {
  const [currentTime, setCurrentTime] = useState(expireIn);

  const getFormattedTime = (time) => {
    const day = Math.floor(time / DAY);
    const hour = Math.floor((time % DAY) / HOUR); // hours left after removing days
    const minutes = Math.floor((time % HOUR) / MINUTE); // minutes left after removing hours
    const seconds = Math.floor((time % MINUTE) / SECOND); // seconds left after removing minutes

    return (
      <Text>
        {day < 10 ? `0${day}` : day} : {hour < 10 ? `0${hour}` : hour} :{" "}
        {minutes < 10 ? `0${minutes}` : minutes} :{" "}
        {seconds < 10 ? `0${seconds}` : seconds}
      </Text>
    );
  };

  useEffect(() => {
    const timerId = setTimeout(() => {
      setCurrentTime(currentTime - 1000);
    }, 1000);

    if (currentTime <= 0) {
      onComplete && onComplete();
      clearTimeout(timerId);
    }

    return () => {
      clearTimeout(timerId);
    };
  }, [currentTime, onComplete]);

  return getFormattedTime(currentTime);
};

Timer.defaultProps = {
  expireIn: 1 * 60 * 1000,
};

export default Timer;

I have used styled-components for styling.

import styled from "styled-components";

export const Text = styled.span`
  display: inline-block;
  font-size: 2em;
  margin: 1em 0;
`;