Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Countdown timer with react hooks

I'm trying to implement countdown timer on my own just to know hooks more. I know there are libraries out there but don't want to use it. the problem with my code is, I cannot get updated state inside "timer" function which is updated in start timer function I'm trying to implement timer that will have triggers to start, stop, & resume & can be manually trigger. by other component that is using the countdown component

import React, { useState } from 'react';

const Countdown = ({ countDownTimerOpt }) => {
  const [getObj, setObj] = useState({
    formatTimer: null,
    countDownTimer: 0,
    intervalObj: null,
  });

  const { formatTimer, countDownTimer, intervalObj } = getObj;

  if (countDownTimerOpt > 0 && intervalObj === null) {
    startTimer();
  }

  function startTimer() {
    const x = setInterval(() => {
      timer();
    }, 1000);

    setObj((prev) => ({
      ...prev,
      countDownTimer: countDownTimerOpt,
      intervalObj: x,
    }));
  }

  function timer() {
    var days = Math.floor(countDownTimer / 24 / 60 / 60);
    var hoursLeft = Math.floor(countDownTimer - days * 86400);
    var hours = Math.floor(hoursLeft / 3600);
    var minutesLeft = Math.floor(hoursLeft - hours * 3600);
    var minutes = Math.floor(minutesLeft / 60);
    var remainingSeconds = countDownTimer % 60;

    const formatTimer1 =
      pad(days) +
      ':' +
      pad(hours) +
      ':' +
      pad(minutes) +
      ':' +
      pad(remainingSeconds);

    if (countDownTimer === 0) {
      clearInterval(intervalObj);
    } else {
      setObj((prev) => ({
        ...prev,
        formatTimer: formatTimer1,
        countDownTimer: prev['countDownTimer'] - 1,
      }));
    }
  }
  function pad(n) {
    return n < 10 ? '0' + n : n;
  }

  return <div>{formatTimer ? formatTimer : Math.random()}</div>;
};
export default Countdown;
import React, { useState, useEffect } from 'react';
import Timer from '../../components/countdown-timer/countdown.component';

const Training = () => {
  const [getValue, setValue] = useState(0);

  useEffect(() => {
    const x = setTimeout(() => {
      console.log('setTimeout');
      setValue(10000);
    }, 5000);

    return () => clearInterval(x);
  }, []);

  return <Timer countDownTimerOpt={getValue} />;

don't want to use any set interval inside training page as the countdown component will also be used in exam page

like image 453
Siddhesh Nayak Avatar asked Jan 19 '26 00:01

Siddhesh Nayak


2 Answers

Usually with hooks I would combine your functionality into a custom hook and use it in different places.

const useTimer = (startTime) => {
    const [time, setTime] = useState(startTime)
    const [intervalID, setIntervalID] = useState(null)
    const hasTimerEnded = time <= 0
    const isTimerRunning = intervalID != null

    const update = () => {
        setTime(time => time - 1)
    }
    const startTimer = () => {
        if (!hasTimerEnded && !isTimerRunning) {
            setIntervalID(setInterval(update, 1000))
        }
    }
    const stopTimer = () => {
        clearInterval(intervalID)
        setIntervalID(null)
    }
    // clear interval when the timer ends
    useEffect(() => {
        if (hasTimerEnded) {
            clearInterval(intervalID)
            setIntervalID(null)
        }
    }, [hasTimerEnded])
    // clear interval when component unmounts
    useEffect(() => () => {
        clearInterval(intervalID)
    }, [])
    return {
        time,
        startTimer,
        stopTimer,
    }
}

You can of course add a reset function or do other changes but use could look like this:

const Training = () => {
    const { time, startTimer, stopTimer } = useTimer(20)
    return <>
        <div>{time}</div>
        <button onClick={startTimer}>start</button>
        <button onClick={stopTimer}>stop</button>
    </>
}
like image 167
ian Avatar answered Jan 21 '26 16:01

ian


You can create a useCountDown Hook as follow (In Typescript) :

Gist

import { useEffect, useRef, useState } from 'react';

export const useCountDown: (
  total: number,
  ms?: number,
) => [number, () => void, () => void, () => void] = (
  total: number,
  ms: number = 1000,
) => {
  const [counter, setCountDown] = useState(total);
  const [startCountDown, setStartCountDown] = useState(false);
  // Store the created interval
  const intervalId = useRef<number>();
  const start: () => void = () => setStartCountDown(true);
  const pause: () => void = () => setStartCountDown(false);
  const reset: () => void = () => {
    clearInterval(intervalId.current);
    setStartCountDown(false);
    setCountDown(total);
  };

  useEffect(() => {
    intervalId.current = setInterval(() => {
      startCountDown && counter > 0 && setCountDown(counter => counter - 1);
    }, ms);
    // Clear interval when count to zero
    if (counter === 0) clearInterval(intervalId.current);
    // Clear interval when unmount
    return () => clearInterval(intervalId.current);
  }, [startCountDown, counter, ms]);

  return [counter, start, pause, reset];
};

Usage Demo: https://codesandbox.io/s/usecountdown-hook-56lqv

like image 43
JT501 Avatar answered Jan 21 '26 15:01

JT501



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!