DEV Community

Cover image for How To Create A Timer With React

How To Create A Timer With React

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 also created the timer using the same setup.

This is the first tutorial I have written. I have done my best to explain each step as accurately as possible 😬.

Table of content

  1. Init React Project
  2. Create Timer Component
  3. Get The Code
  4. Get Advanced Code

1. Init React Project

If you want to add the timer to an existing project, you can skip this part. If you don't have a React project set up yet, use your console to go to the folder where you want to save your project by using

cd folder-name

and write

npx create-react-app my-app

in the console to initialize the project. After the project is ready, write

cd my-app

to go into the project folder,

code .

to open it in Visual Studio Code (if you are using this IDE), and

npm start

to run the project in localhost:3000.

You can find further instructions under create react app or the official React docs.

2. Create Timer Component

In your src folder, create a new file called Timer.js.

Then, create a React arrow function component with the same name as the file and add the return statement. Don't forget to export the function.

// Timer.js

import React from 'react';

const Timer = () => {

  return (
    <div className="timer">
    </div>
  );
};

export default Timer;
Enter fullscreen mode Exit fullscreen mode

Initialize variables

We start the function by importing the useState() hook (a built-in function) from React and creating the states we need. For a timer of this length we need days, hours, minutes and seconds. I have set the default value to 0 since we are dealing with integers.

// Timer.js

import React from 'react';
import { useState } from 'react';

const Timer = () => {
  const [days, setDays] = useState(0);
  const [hours, setHours] = useState(0);
  const [minutes, setMinutes] = useState(0);
  const [seconds, setSeconds] = useState(0);

  return (
    <div className="timer">
    </div>
  );
};

export default Timer;
Enter fullscreen mode Exit fullscreen mode

Initialize deadline

We need a specific date for how long the timer should run. In this case, I chose New Year's Eve and declared it as a string value (which we will later convert to an integer) in a variable called deadline.

// Timer.js

import React from 'react';
import { useState } from 'react';

const Timer = () => {
  const [days, setDays] = useState(0);
  const [hours, setHours] = useState(0);
  const [minutes, setMinutes] = useState(0);
  const [seconds, setSeconds] = useState(0);
  const [seconds, setSeconds] = useState(0);

  const deadline = "December, 31, 2022";

  return (
    <div className="timer">
    </div>
  );
};

export default Timer;
Enter fullscreen mode Exit fullscreen mode

Create function to get time

Now let's create a function that will calculate the time until this specific date. We need our declared variable deadline in this function and can directly use it since it is declared within the scope of the Timer component.

To get the difference between today and our deadline, I created a new variable called time and set its value to the value of deadline minus the value of the current date.

To do this, we use JavaScript's built-in function Date.parse(), which converts the declared string deadline that we passed as prop into an integer that we can work with.

The Date.now() method returns the number of milliseconds representing the current date.

// Timer.js

import React from 'react';
import { useState } from 'react';

const Timer = () => {
  const [days, setDays] = useState(0);
  const [hours, setHours] = useState(0);
  const [minutes, setMinutes] = useState(0);
  const [seconds, setSeconds] = useState(0);

  const deadline = "December, 31, 2022";

  const getTime = () => {
    const time = Date.parse(deadline) - Date.now();

  return (
    <div className="timer">
    </div>
  );
};

export default Timer;
Enter fullscreen mode Exit fullscreen mode

Calculate values for variables

Since time is an int in milliseconds, we can now calculate and set the value of the days, hours, minutes and seconds until the deadline.

To get the values for each variable, we need to convert the milliseconds to seconds by dividing time by 1000 (because 1000 milliseconds is 1 second).

To get the minutes, we have to divide time by 1000 (to get the seconds) and divide by 60 (because 1 minute has 60 seconds).

To get the hours, we must divide time in milliseconds by 1000 (to get the seconds), 60 (to get the minutes), and 60 again (because 1 hour has 60 minutes), summarized here in parentheses to 1000 * 60 * 60 (abbreviated 3.600.000 can be used).

To get the days, we need to divide time in milliseconds by 1000 (to get the seconds), 60 (to get the minutes), 60 again (because 1 hour has 60 minutes), and 24 (because 1 day has 24 hours), summarized here in parentheses as 1000 * 60 * 60 * 24 (abbreviated 86.400.000 can be used).

After getting each value, we use the remainder (%) operator to reset the values to 0 if, for example, the user passed 86.400.000 as milliseconds, which equals 24 hours.

By default, the function rolls over the hours if they are greater than 24, e.g. if the milliseconds are 36 hours, the hours = hours % 24 line sets the hours to 12. Depending on your use case, you may not want to roll over the hours.

The built-in function Math.floor() returns the largest integer that is less than or equal to the specified value.

// Timer.js

import React from 'react';
import { useState } from 'react';

const Timer = () => {
  const [days, setDays] = useState(0);
  const [hours, setHours] = useState(0);
  const [minutes, setMinutes] = useState(0);
  const [seconds, setSeconds] = useState(0);

  const deadline = "December, 31, 2022";

  const getTime = () => {
    const time = Date.parse(deadline) - Date.now();

    setDays(Math.floor(time / (1000 * 60 * 60 * 24)));
    setHours(Math.floor((time / (1000 * 60 * 60)) % 24));
    setMinutes(Math.floor((time / 1000 / 60) % 60));
    setSeconds(Math.floor((time / 1000) % 60));
  };

  return (
    <div className="timer">
    </div>
  );
};

export default Timer;
Enter fullscreen mode Exit fullscreen mode

To get the timer to render after every second, we need to import the useEffect() hook and use it inside the component to get the current value. This hook is used when a state is going to be updated.

We will use the setInterval() method, which calls our getTime() function with the deadline passed as prop, with a fixed time delay between each call of 1000 (in milliseconds, which is 1 second).
And since this function is only called when the component is unmounted, we must clear the interval using the clearInterval() function to invoke a rerender.

// Timer.js

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

const Timer = () => {
  const [days, setDays] = useState(0);
  const [hours, setHours] = useState(0);
  const [minutes, setMinutes] = useState(0);
  const [seconds, setSeconds] = useState(0);

  const deadline = "December, 31, 2022";

  const getTime = () => {
    const time = Date.parse(deadline) - Date.now();

    setDays(Math.floor(time / (1000 * 60 * 60 * 24)));
    setHours(Math.floor((time / (1000 * 60 * 60)) % 24));
    setMinutes(Math.floor((time / 1000 / 60) % 60));
    setSeconds(Math.floor((time / 1000) % 60));
  };

  useEffect(() => {
    const interval = setInterval(() => getTime(deadline), 1000);

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

  return (
    <div className="timer">
    </div>
  );
};

export default Timer;
Enter fullscreen mode Exit fullscreen mode

And that's it.

The functionality of the timer is now fully set up. You can now style each variable inside the return statement of the timer component <div className="timer></div> the way you want it. You can take a look at how I styled my code. Don't forget to import the timer component into the app component so it can be rendered.

3. Get The Code

You can find the code and stylesheet embedded in the CodePen. Please note that there are therefore differences in the structure, as I mentioned in the tutorial. There are no additional files in the CodePen and all the code is in the JavaScript sections. Also, no imports have been made and the hooks start with React.useState and React.useEffect.

4. Get Advanced Code

Luke Shiru has added a much more advanced code approach to my timer in the comments that I want to share with anyone who is more familiar with JavaScript or wants to be. Thanks again for sharing the code and making my article even better.

See Luke Shiru's approach
import { useEffect, useMemo, useState } from "react";

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

export const Timer = ({ deadline = new Date().toString() }) => {
    const parsedDeadline = useMemo(() => Date.parse(deadline), [deadline]);
    const [time, setTime] = useState(parsedDeadline - Date.now());

    useEffect(() => {
        const interval = setInterval(
            () => setTime(parsedDeadline - Date.now()),
            1000,
        );

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

    return (
        <div className="timer">
            {Object.entries({
                Days: time / DAY,
                Hours: (time / HOUR) % 24,
                Minutes: (time / MINUTE) % 60,
                Seconds: (time / SECOND) % 60,
            }).map(([label, value]) => (
                <div key={label} className="col-4">
                    <div className="box">
                        <p>{`${Math.floor(value)}`.padStart(2, "0")}</p>
                        <span className="text">{label}</span>
                    </div>
                </div>
            ))}
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode


Thank you

Thanks for your reading and time. I really appreciate it!

Discussion (15)

Collapse
lukeshiru profile image
Luke Shiru • Edited on

Ideally you shouldn’t have one state for every part of the time, and instead you should have only one for time and the rest comes from that:

import { useEffect, useMemo, useState } from "react";

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

export const Timer = ({ deadline = new Date().toString() }) => {
    const parsedDeadline = useMemo(() => Date.parse(deadline), [deadline]);
    const [time, setTime] = useState(parsedDeadline - Date.now());

    useEffect(() => {
        const interval = setInterval(
            () => setTime(parsedDeadline - Date.now()),
            1000,
        );

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

    return (
        <div className="timer">
            {Object.entries({
                Days: time / DAY,
                Hours: (time / HOUR) % 24,
                Minutes: (time / MINUTE) % 60,
                Seconds: (time / SECOND) % 60,
            }).map(([label, value]) => (
                <div key={label} className="col-4">
                    <div className="box">
                        <p>{`${Math.floor(value)}`.padStart(2, "0")}</p>
                        <span className="text">{label}</span>
                    </div>
                </div>
            ))}
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode

Here's the Codepen:

Cheers!

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
Collapse
lukeshiru profile image
Luke Shiru

Extracting the logic to a hook is a great approach! The only thing I would change is that setInterval/setTimeout are known for not keeping in sync with time after a while (they aren't precise, you can watch a video about that here), so after a while that will get out of sync. That's why I used something similar to the post and I just get the time again from Date instead of calculating it myself. But other than that, the suggestion of moving the logic to a hook is actually great.

Thread Thread
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
π•π•¦π•π•šπ•’ πŸ‘©πŸ»β€πŸ’» Author

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 • Edited on

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
π•π•¦π•π•šπ•’ πŸ‘©πŸ»β€πŸ’» Author

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

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
π•π•¦π•π•šπ•’ πŸ‘©πŸ»β€πŸ’» Author

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
moopet profile image
Ben Sinclair

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

Collapse
yuridevat profile image
π•π•¦π•π•šπ•’ πŸ‘©πŸ»β€πŸ’» Author

Copy paste 😌. Very attentive, thank you!

Collapse
yuridevat profile image
π•π•¦π•π•šπ•’ πŸ‘©πŸ»β€πŸ’» Author

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