DEV Community

Cover image for How To Create A Timer With React

How To Create A Timer With React

Julia πŸ‘©πŸ»β€πŸ’» GDE on July 27, 2022

For one of my current projects, I created a timer until the release on New Year's Eve - December 31, 2022. Since the project is written in React, I...
Collapse
 
raibtoffoletto profile image
RaΓ­ B. Toffoletto

Nice beginner article @yuridevat and great comment @lukeshiru . I also would rather store the unix timespan than the values individually, it makes easier to do math with it and you wouldn't need even to setTime a new timespan every second, a simple subtraction would do the trick. =D

Building on that, here's a handy hook:

import { useState, useEffect } from "react";

const SECOND = 1_000;
const MINUTE = SECOND * 60;
const HOUR = MINUTE * 60;
const DAY = HOUR * 24;

export default function useTimer(deadline, interval = SECOND) {
  const [timespan, setTimespan] = useState(new Date(deadline) - Date.now());

  useEffect(() => {
    const intervalId = setInterval(() => {
      setTimespan((_timespan) => _timespan - interval);
    }, interval);

    return () => {
      clearInterval(intervalId);
    };
  }, [interval]);

  /* If the initial deadline value changes */
  useEffect(() => {
    setTimespan(new Date(deadline) - Date.now());
  }, [deadline]);

  return {
    days: Math.floor(timespan / DAY),
    hours: Math.floor((timespan / HOUR) % 24),
    minutes: Math.floor((timespan / MINUTE) % 60),
    seconds: Math.floor((timespan / SECOND) % 60)
  };
}
Enter fullscreen mode Exit fullscreen mode

Then its simple to use:

const { days, hours, minutes, seconds } = useTimer("2022-12-31T23:59:59");
Enter fullscreen mode Exit fullscreen mode
 
raibtoffoletto profile image
RaΓ­ B. Toffoletto

Yes good point! But as far I remember it deviates very irregularly in the milliseconds, so I tend to ignore it unless it's crucial πŸ˜…. But thanks for the video link, I'll watch it to refresh my memory on the subject 😁.

In that case only one useEffect is needed:

useEffect(() => {
    const intervalId = setInterval(() => {
      setTimespan(new Date(deadline) - Date.now());
    }, interval);

    return () => {
      clearInterval(intervalId);
    };
  }, [deadline, interval]);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
yuridevat profile image
Julia πŸ‘©πŸ»β€πŸ’» GDE

Wow! Thank you for your suggestion! Since my tutorial is a beginner tutorial, I won't change it, but will include your comment in the article, because it could be very important for those who want to write the code in a professional way and this comment must not be overlooked in any case!

I myself will study your solution to fully understand it. It's time for me to finally write code in a professional way! You really made my day, thanks again!

Collapse
 
syeo66 profile image
Red Ochsenbein (he/him) • Edited

May I suggest a few things?

  • Date.now() would do the trick instead of Date.parse(new Date()).
  • let interval could be const interval. It's never reassigned.
  • Since you already define deadline within the scope of Timer there is no need to use it as a parameter for the getTime function.
  • I'd probably only use one state called timeDiff and use an object in the state
const getTime = () => {
    const time = new Date(deadline) - new Date();

    setTimeDelta({
        days: Math.floor(time / (1000 * 60 * 60 * 24)),
        hours: Math.floor((time / (1000 * 60 * 60)) % 24),
        minutes: Math.floor((time / 1000 / 60) % 60),
        seconds: (Math.floor((time / 1000) % 60)),
    })
};
Enter fullscreen mode Exit fullscreen mode

With React 18's Automatic Batching this is no longer that important, but I feel it's better to have less different state variables if they are always dependent.
Also this approach would make it easier to have really pure getTime function which only returns the object used to set the new state.

To repeat this function, we must clear the interval after each rendering using the clearInterval() function.

This is not entirely true: the returned function in useEffecxt is only called when the component is unmounted (or on changes in the deps, but since there are none in this case...)

Collapse
 
yuridevat profile image
Julia πŸ‘©πŸ»β€πŸ’» GDE

Thank you so much for your suggestions, I will insert most of your suggestions (the ones I get πŸ˜…) right away!

Doh', I thought that I did not fully understand setInterval/clearInterval and useEffect. I will read about it again and update the information.

Thank you for helping me making this tutorial better, I really appreciate it.

Collapse
 
syeo66 profile image
Red Ochsenbein (he/him)

If you have any questions feel free to reach out.

Collapse
 
francoisaudic profile image
francoisaudic

For the HTML, you could put a Β« timer Β» role and an attribute tabindex="0" on the container of the timer.

Collapse
 
yuridevat profile image
Julia πŸ‘©πŸ»β€πŸ’» GDE

Shame on me. Thank you very much for this advice, Francois. I should indeed include accessible code in my (future) tutorials from the beginning, and not only for my accessibility articles themselves. I will do my research and implement it. I will keep you posted when I have revised the code.

Thank you for supporting me in getting better at accessibility every day.

Collapse
 
samuel_kelv profile image
Samuel Kelv

The explanation is beginner friendly. Kudos

Collapse
 
yuridevat profile image
Julia πŸ‘©πŸ»β€πŸ’» GDE

Thanks, Samuel.

Collapse
 
moopet profile image
Ben Sinclair

Hi, you're missing a closing quote everywhere you mention <div className="timer> :)

Collapse
 
yuridevat profile image
Julia πŸ‘©πŸ»β€πŸ’» GDE

Copy paste 😌. Very attentive, thank you!

Collapse
 
yuridevat profile image
Julia πŸ‘©πŸ»β€πŸ’» GDE

Hey Kaio! Could you please remove the link in your comment since this is not the right place to post it, thanks!

Collapse
 
joniedev profile image
Okoro John

Nice peace. Many thanks.