// Tutorial //

How to Create a Countdown Timer with React Hooks

Published on November 20, 2019 · Updated on December 6, 2021
Default avatar
By joshtronic
Developer and author at DigitalOcean.
How to Create a Countdown Timer with React Hooks

Introduction

In this tutorial, you will create a countdown timer using React hooks to update state and manage side effects in a React component.

With React hooks, you can create cleaner code, reusable logic between components, and update state without classes.

Countdown timers are a common UI component. They can communicate to users how long they have been doing something or how much time until some event happens. The event you will countdown to in this tutorial is DigitalOcean’s Hacktoberfest.

By the end of this tutorial, you will have a functional and reusable Countdown timer using React’s useState() and useEffect() hooks.

Screenshot of the completed timer counting down to Hacktoberfest

Prerequisites

Before you begin this guide, you’ll need the following:

This tutorial was verified with Node.js v16.13.1, npm v8.2.0, and react v17.0.2.

Step 1 — Creating an Empty Project

In this step, you’ll create a new project using Create React App. Then you will delete the sample project and related files that are installed when you bootstrap the project.

To start, make a new project. In your terminal, run the following script to install a fresh project using create-react-app:

  1. npx create-react-app react-hooks-timer

After the project is finished, change into the directory:

  1. cd react-hooks-timer

In a new terminal tab or window, start the project using the Create React App start script. The browser will auto-refresh on changes, so leave this script running while you work:

  1. npm start

You will get a local running server. If the project did not open in a browser window, you can open it with http://localhost:3000/. If you are running this from a remote server, the address will be http://your_server_ip:3000.

Your browser will load with a React application generated by Create React App:

React template project

You will be building a new set of custom components, so you’ll need to start by clearing out some boilerplate code so that you can have an empty project.

To start, open src/App.js in a text editor. This is the root component that is injected into the page. All components will start from here. You can find more information about App.js at How To Set Up a React Project with Create React App.

Open src/App.js with the following command:

  1. nano src/App.js

You will see a file like this:

react-hooks-timer/src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

Then replace everything in the return statement to return a set of <div> tags. This will give you a valid page that returns nothing. The final code will look like this:

react-hooks-timer/src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div>
    </div>
  )
}

export default App;

Next, remove the logo. Delete the line import logo from './logo.svg';.

Save and exit the text editor.

Finally, delete the logo since you won’t be using it in this application. It’s a good practice to remove unused files as you work to avoid confusion.

In the terminal window type the following command:

  1. rm src/logo.svg

Now that the project is set up, you can create your first component.

Step 2 — Calculating How Much Time is Left

In this step, you will create a function that calculates the time remaining between the current date and the first day of Hacktoberfest.

First, set up a function called calculateTimeLeft:

react-hooks-timer/src/App.js
// ...

const calculateTimeLeft = () => {

};

// ...

Next, inside the function, you will use the JavaScript Date object to find the current year.

Create a variable called year that is set to the JavaScript date method Date.getFullYear().

Add the following code inside the calculateTimeLeft function:

react-hooks-timer/src/App.js
// ...

const calculateTimeLeft = () => {
  let year = new Date().getFullYear();
}

// ...

Note: You can use the JavaScript Date object to work with dates and times.

The Date.getFullYear() method will grab the current year.

You can now use this variable to calculate the difference between the current date and the first day of Hacktoberfest.

Inside the calculateTimeLeft function, add a new variable called difference. Set it equal to a new Date object with the following code:

react-hooks-timer/src/App.js
// ...

const calculateTimeLeft = () => {
  let year = new Date().getFullYear();

  const difference = +new Date(`10/01/${year}`) - +new Date();
}

// ...

The + before the new Date object is shorthand to tell JavaScript to cast the object as an integer, which gives you the object’s Unix timestamp represented as microseconds since the epoch.

Note: For this tutorial, make sure the date you are counting down to is set in the future or you will encounter an error.

To keep the code reusable, you use a JavaScript Template Literal and add in the year variable along with the month and day of Hacktoberfest. Hacktoberfest starts on October 1st each year. When you use the year variable in place of a hard-coded year, you will always have the current year.

Now that you calculated the total number of milliseconds until the countdown timer expires, you need to convert the number of milliseconds to something more friendly and human-readable.

Step 3 — Formatting to Days, Hours, Minutes, and Seconds

In this step, you will create an empty object called timeLeft, use an if statement to check if there is time remaining, and calculate the total number of hours, minutes, and seconds by using math and the modulus (%) operator. Finally, you will return the timeLeft.

First, create the empty object called timeLeft which will then be filled in with days, hours, minutes, and seconds in the if statement.

Add the following code inside the calculateTimeLeft function:

react-hooks-timer/src/App.js
// ...

const calculateTimeLeft = () => {
  let year = new Date().getFullYear();
  let difference = +new Date(`10/01/${year}`) - +new Date();

  let timeLeft = {};
}

// ...

Now create an if statement that will compare the difference variable to see if it is greater than 0.

Add this code inside the calculateTimeLeft function:

react-hooks-timer/src/App.js
// ...

const calculateTimeLeft = () => {
  let year = new Date().getFullYear();
  let difference = +new Date(`10/01/${year}`) - +new Date();

  let timeLeft = {};

  if (difference > 0) {
    timeLeft = {
      days: Math.floor(difference / (1000 * 60 * 60 * 24)),
      hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
      minutes: Math.floor((difference / 1000 / 60) % 60),
      seconds: Math.floor((difference / 1000) % 60)
    };
  }
}

// ...

In this code, you round the numbers from the day, hours, minutes, and seconds down and drop the remainder to get a whole number value. You can then compare the difference to see if it is greater than 0.

Finally, you need to return timeLeft so that you can use the value elsewhere in the component.

Add this code inside the calculateTimeLeft function:

react-hooks-timer/src/App.js
// ...

const calculateTimeLeft = () => {
  let year = new Date().getFullYear();
  let difference = +new Date(`10/01/${year}`) - +new Date();

  let timeLeft = {};

  if (difference > 0) {
    timeLeft = {
      days: Math.floor(difference / (1000 * 60 * 60 * 24)),
      hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
      minutes: Math.floor((difference / 1000 / 60) % 60),
      seconds: Math.floor((difference / 1000) % 60)
    };
  }

  return timeLeft;
}

// ...

Now that you have created a function that calculates the time left until Hacktoberfest, you can add in the app state that will control and update your timer.

Step 4 — Updating Your App State with useState and useEffect

With React Hooks, you can add state management capabilities to existing functional components without converting them to a class.

In this step, you will import the useState and useEffect hooks from React to manage state in this component.

At the top of the App.js file, add useState and useEffect in your import statement:

react-hooks-timer/src/App.js
import React, { useEffect, useState } from "react";

// ...

This code tells React that you want to use these specific hooks and their functionality that is available from React.

To make the countdown timer work, you will need to wire up the time remaining method we previously coded to update the state:

Add this code after the calculateTimeLeft function:

react-hooks-timer/src/App.js
// ...

const [timeLeft, setTimeLeft] = useState(calculateTimeLeft());

// ...

This JavaScript syntax is called array destructuring.

The useState method accepts a parameter to set the initial state and returns an array containing the current state and a function to set the state.

timeLeft will carry our time left object of intervals and provide us with a method to set the state. On component load, the timeLeft value is set to the current time left value.

Next, you will use the useEffect hook to deal with the component side effects.

Note: A side effect is anything that affects something outside the scope of the function being executed.

In this solution, you will use a setTimeout method inside of the useEffect hook. setTimeout and the similar setInterval method are common React patterns when used inside of the useEffect hook.

Most async behaviors like the setTimeout method in React are defined with a combination of the useEffect and useState hooks.

Note: You can read more about when and how to use methods like setTimeout and setInterval in this section of the React Docs.

Add this code after the useState() function:

react-hooks-timer/src/App.js
// ...

const [timeLeft, setTimeLeft] = useState(calculateTimeLeft());

useEffect(() => {
  const timer = setTimeout(() => {
    setTimeLeft(calculateTimeLeft());
  }, 1000);
});

// ...

The useEffect is what updates the amount of time remaining. By default, React will re-invoke the effect after every render.

Every time the variable timeLeft is updated in the state, the useEffect fires. Every time that fires, we set a timer for 1 second (or 1,000ms), which will update the time left after that time has elapsed.

The cycle will continue every second after that.

To help to eliminate the potential of stacking timeouts and causing an error, add the clearTimeout method inside the useEffect hook as well.

Add a clearTimeout method and pass in the variable timer as a parameter:

react-hooks-timer/src/App.js
// ...

useEffect(() => {
  const timer = setTimeout(() => {
    setTimeLeft(calculateTimeLeft());
  }, 1000);

  return () => clearTimeout(timer);
});

// ...

The return function runs every time the useEffect runs the timer except for the first run of the component and will clear out the timeout if the component is unmounted.

Now that your state is set to the calculateTimeLeft() object and is updating inside your effect hook, it can be used to build your display component.

Step 5 — Using Object.keys

In this step, you will use Object.keys to iterate over the timeLeft object and build out a display component. You will use the display component to show the time left before Hacktoberfest begins.

First, create a new variable under the useEffect hook called timerComponents:

react-hooks-timer/src/App.js
// ...

const timerComponents = [];

// ...

After iterating over the keys in timeLeft, you will use this variable to push a new JSX component with the time left.

Next, use Object.keys to iterate over the timeLeft object you returned from your calculateTimeLeft function.

Add this code in the timerComponents variable:

react-hooks-timer/src/App.js
// ...

const timerComponents = [];

Object.keys(timeLeft).forEach((interval) => {
  if (!timeLeft[interval]) {
    return;
  }

  timerComponents.push(
    <span>
      {timeLeft[interval]} {interval}{" "}
    </span>
  );
});

// ...

Here the code loops through the properties of the timeLeft object. If the timer interval has a value greater than zero, it adds an element to the timerComponents array.

Note: The extra {" "} in the code is used so that the intervals that display the time left do not run into each other when displayed on the screen.

The {} allow you to use JavaScript inside your JSX and the "" add the space.

Now you are ready to add the new JSX in the App components return statement to display the time left until Hacktoberfest.

Step 6 — Displaying the Time Left

In this step, you will add JSX components to the app component’s return statement. You will use a ternary operator to check if there is time left or if it is time for Hacktoberfest,

To use the timerComponents array, you need to check its length and either return it or let the user know that the timer has already elapsed.

Add this code inside the return statement:

react-hooks-timer/src/App.js
// ...

return (
  <div>
    {timerComponents.length ? timerComponents : <span>Time's up!</span>}
  </div>
);

// ...

In React JSX components, you use a ternary operator in place of a JavaScript if statement. This is because only expressions are allowed inside JSX.

The timerComponents.length line of code checks to see if there is anything inside the timerComponents array and renders it if there is, otherwise it renders Time's up!.

Next, you will add two more JSX components to the return statement to let the user know what they are counting down:

react-hooks-timer/src/App.js
// ...

return (
  <div>
    <h1>Hacktoberfest 2020 Countdown</h1>
    <h2>With React Hooks!</h2>
    {timerComponents.length ? timerComponents : <span>Time's up!</span>}
  </div>
);

// ...

To use the current year instead of hard coding 2020, you can create a new state variable and set the initial state to new Date().getFullYear();.

After the first useState() variable, add this code:

react-hooks-timer/src/App.js
// ...

const [timeLeft, setTimeLeft] = useState(calculateTimeLeft());
const [year] = useState(new Date().getFullYear());

// ...

This method will grab the current year as you used in the calculateTimeLeft function.

You can then remove the hardcoded 2020 from your h1 and replace it with year:

react-hooks-timer/src/App.js
// ...

return (
  <div>
    <h1>Hacktoberfest {year} Countdown</h1>
    <h2>With React Hooks!</h2>
    {timerComponents.length ? timerComponents : <span>Time's up!</span>}
  </div>
);

// ...

This will display your state variable, which will now always have the current year. Your completed project will look like this:

Completed timer counting down to Hacktoberfest

Check out this GitHub repository to see the full code.

Conclusion

In this tutorial, you built a countdown UI component using the useState and useEffect hooks to manage and update your application’s state.

From here, you can continue your learning with styling React components to create a more attractive countdown UI.

You can also follow the full How To Code in React.js series on DigitalOcean to learn even more about developing with React.

Want to deploy your application quickly? Try Cloudways, the #1 managed hosting provider for small-to-medium businesses, agencies, and developers - for free. DigitalOcean and Cloudways together will give you a reliable, scalable, and hassle-free managed hosting experience with anytime support that makes all your hosting worries a thing of the past. Start with $100 in free credits!

Learn more here


About the authors
Default avatar
Developer and author at DigitalOcean.

Default avatar
Developer and author at DigitalOcean.

Still looking for an answer?

Was this helpful?
2 Comments

This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

This comment has been deleted

It looks like there’s a somewhat serious issue with your component: You have recursive timeouts that can be triggered in many different ways. If you’re not careful, you will end up with hundreds or thousands of timeouts running concurrently