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.
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.
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
:
- npx create-react-app react-hooks-timer
After the project is finished, change into the directory:
- 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:
- 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:
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:
- nano src/App.js
You will see a file like this:
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:
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:
- rm src/logo.svg
Now that the project is set up, you can create your first component.
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
:
// ...
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:
// ...
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:
// ...
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.
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:
// ...
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:
// ...
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:
// ...
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.
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:
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:
// ...
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:
// ...
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:
// ...
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.
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
:
// ...
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:
// ...
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.
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:
// ...
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:
// ...
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:
// ...
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
:
// ...
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:
Check out this GitHub repository to see the full code.
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
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