A React logo on a timer, with confetti falling around it.

Build a React timer component using the useState and useEffect Hooks in minutes. A React timer component is a great way to learn React, so let’s begin!

What We’re Building

Today we’re going to build a React timer component. I’m sure you’ve all used timers before and know how they work. You click a button to start a timer, click it again to pause the timer, and finally, click the reset button to reset the timer back to zero seconds.

If you’re new to React, this tutorial will be a great starting point. Building a simple app like this React timer is a great way to learn how to create web apps in React, and how to code in JavaScript.

The whole React component and CSS code is below. Skip past the code to Scaffolding the React Timer app if you’d like to go through the whole tutorial from start to finish.

Timer.js
import React, { useState, useEffect } from 'react'; const Timer = () => { const [seconds, setSeconds] = useState(0); const [isActive, setIsActive] = useState(false); function toggle() { setIsActive(!isActive); } function reset() { setSeconds(0); setIsActive(false); } useEffect(() => { let interval = null; if (isActive) { interval = setInterval(() => { setSeconds(seconds => seconds + 1); }, 1000); } else if (!isActive && seconds !== 0) { clearInterval(interval); } return () => clearInterval(interval); }, [isActive, seconds]); return ( <div className="app"> <div className="time"> {seconds}s </div> <div className="row"> <button className={`button button-primary button-primary-${isActive ? 'active' : 'inactive'}`} onClick={toggle}> {isActive ? 'Pause' : 'Start'} </button> <button className="button" onClick={reset}> Reset </button> </div> </div> ); }; export default Timer;
timer.css
.app { text-align: center; background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .time { font-size: 3rem; padding: 2rem; } .button { padding: .6rem 1.5rem; margin: .4rem; border-radius: 3px; text-transform: uppercase; font-weight: 600; font-size: .8rem; border-style: groove; } .button:focus { outline-width: 0; } .button-primary:hover { background-color: #2641d4; border: 1px solid #1b1f2b; } .button-primary-active { background-color: #3151ff; border: 1px solid #152684; color: white; } .button-primary-inactive { background-color: #3151ff; border: 1px solid #152684; color: white; }

Scaffolding Out the React Timer Component

Let’s begin by writing the HTML that will display our React timer component. I’ve used some custom CSS classes which you can choose to use. If so, the code is above.

Timer.js
import React from 'react'; const Timer = () => { return ( <div className="app"> <div className="time"> seconds </div> <div className="row"> <button className="button-primary"> Start </button> <button className="button-secondary"> Reset </button> </div> </div> ); }; export default Timer;

Now that we’ve got the basic structure to our timer component completed, let’s give it state using the useState React Hook.

Adding State using the useState React Hook

Since React 16.8, functional components are able to become stateful thanks to React Hooks. Hooks provide functional components things like state and lifecycle methods which were not otherwise possible without using a Class component.

Therefore, we need to import the useState Hook into our functional component.

Timer.js
import React, { useState } from 'react';

Now that we’ve imported the useState Hook, we’re now able to initialize two states: seconds and isActive. Seconds will store the value of our timer, whereas isActive will store the timer’s state for whether it is currently timing or paused.

Timer.js
... const Timer = () => { const [seconds, setSeconds] = useState(0); const [isActive, setIsActive] = useState(false); ...

We start the seconds off at 0, and the timer in paused state (isActive set to false).

Finally, let’s bring in our new stateful values into the HTML we wrote earlier, to change the user interface based on the current state:

Timer.js
... return ( <div className="app"> <div className="time"> {seconds}s </div> <div className="row"> <button className={`button button-primary button-primary-${isActive ? 'active' : 'inactive'}`}> {isActive ? 'Pause' : 'Start'} </button> <button className="button"> Reset </button> </div> </div> ); ...

Starting, Pausing and Resetting the Timer

You might have noticed that while our Timer app looks nice, it doesn’t function at all yet. Let’s change that!

First, let’s add a new function to the body of our functional component called toggle. When the toggle function is called it will change the value of isActive to be the opposite of what it currently is.

function toggle() { setIsActive(!isActive); }

๐Ÿค”

This may look strange, but it’s the cleanest and simplest way of writing a function like this. Rather than writing two separate functions named start and pause, we’ve combined them into one function and named it toggle.

However, we do need a separate function to reset the timer, like so:

function reset() { setSeconds(0); setIsActive(false); }

Finally, add let’s hook up these two functions with the two buttons onClick handlers:

... <div className="row"> <button className={`button button-primary button-primary-${isActive ? 'active' : 'inactive'}`} onClick={toggle}> {isActive ? 'Pause' : 'Start'} </button> <button className="button" onClick={reset}> Reset </button> </div> ...

Starting the React Timer with the useEffect Hook

The last piece of the puzzle is to start the timer. For that, we’re going to use the setInterval method.

If you’d like to learn more about setInterval, I recommend reading setInterval in React Components Using Hooks.

Rather than starting the setInterval timer in the toggle function, we’re going to use the useEffect React Hook to detect when isActive is true and start the timer inside of that function:

... useEffect(() => { let interval = null; if (isActive) { interval = setInterval(() => { setSeconds(seconds => seconds + 1); }, 1000); } else if (!isActive && seconds !== 0) { clearInterval(interval); } return () => clearInterval(interval); }, [isActive, seconds]); ...

Firstly, we initialize a new variable interval to null. Then, we detect if isActive is true. If it is, we assign the previously created interval variable to a new interval that triggers every 1,000 milliseconds.

Inside the interval is where we increment the seconds value by one.

If the isActive value is false, then we’re clearing out the interval (like the responsible developers we are ๐Ÿค“). We’re also returning clearInterval out of the useEffect method, again, to cleanup after ourselves. This is the equivalent of calling componentWillUnmount in a React Class component.

๐Ÿ† Most Read

๐Ÿ“ฌ The Monthly Upmostly Newsletter

One email a month, packed with the latest React tutorials, delivered straight to your inbox.
Zero spam, just great content. Unsubscribe at any time.
James King headshot
๐Ÿ‘‹ Hey, I'm James King
My tutorials help 60,000+ developers learn React and JavaScript every month. If you'd like to receive a friendly email once in a while of all new React tutorials, just pop your email above! I appreciate the support!

๐Ÿ’ฌ Leave a comment

Your email address will not be published. Required fields are marked *

We will never share your email with anyone else.

Comments

Chris Ryland says:

setSeconds(seconds => seconds + 1)’

could also be expressed more simply as

setSeconds(seconds + 1)

no? Since the enclosing function is already deferring until the update point.

James King says:

We’re performing a functional update on the seconds state value, that’s why we’re passing in a function to setState. It’ll receive the original value, make a change to it, and update the state.

https://reactjs.org/docs/hooks-reference.html#usestate

Jack says:

Like this, useEffect is called after every update, right? Because of the ‘seconds’ in the second useEffect argument. And then the setInterval is reset and started again.
I’m not 100% sure though… That’s what I would have done:
useEffect(() => {
if(isActive) {
const interval = setInterval(…);
return () => clearInterval(interval);
}
}, [isActive]);

(Also not sure if you can only return a callback conditionally..)

Dave Irwin says:

James, thanks for a great tutorial. It was exactly what I needed for a school project. Newbie question here: what is the [isActive, seconds] doing at the end of the useEffect function?

James King says:

Hi Dave, and welcome to the site!

The array at the end of that particular useEffect function is what’s called the dependency array. Whenever any of the values passed into that array change, that useEffect function will execute again. It’s the equivalent to the componentDidUpdate method in a React Class Component. It’s also good practice to put any variables that you use inside of the useEffect function into the dependency array.

Clark Williams says:

I am making a countdown timer, like a game clock. I got it working with seconds, but how do I make it minutes:seconds, like a real game clock?

James King says:

Surprisingly, that’s a little more difficult. Time and Date is a real pain in the backside when it comes to coding. What I suggest you do is use a library called Moment to help you out.

Neil says:

The fact, that the interval is re-instantiated every second makes me think, that this approach is less performant than the old componentDidMount and componentDidUnmount. Ideas?

James King says:

Those are two completely separate concepts. setInterval can be used inside of a componentDidMount method, just as easily and performant as using it inside of the useEffect Hook.

Hernan says:

James, thank you, I’m just getting started with React and coding in general, so thanks for this great tutorial that helped me a lot.
In the useEffect ยฟWhy do you return clearInterval of “Interval” outside the method?
It doesn’t work right without that, but I donยดt get why…

James King says:

That’s the useEffect equivalent of the life-cycle method componentWillUnmount.