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.

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;
.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.

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.

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.

... 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:

... 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.

Thank you for following along and if you want to learn more about the differences between React Class components and functional components check out this guide.

Avatar photo
👋 Hey, I'm James Dietrich
I work full-time at an AI-based startup out of San Francisco, CA. My true passion is to help others. My tutorials help 150,000+ developers learn React and JavaScript every month. Follow on Twitter, or Github.

💬 Leave a comment

Your email address will not be published.

We will never share your email with anyone else.


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.

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..)

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?

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.

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?

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.

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?

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.

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…

thank you so much for this! i needed something like this for a school project and didnt want to import anything if possible but i was having trouble with the useEffect side of things. i actually reworked it into a countdown by adding a timerVal state variable that could be set via user input and resets to that value when time is up, seems to be working fine but let me know if you have any suggestions. thanks again

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

useEffect(() => {
if (seconds === 0) {
alert(“times up”);
}, [isActive]);

Hello. I used your example to create an app that scrolls through different “dashboards” (pages) and displays data. If there is not enough data to cycle through an interval, it goes into an infinite loop. Any suggestions on how I can stop this?

Thanks for the example. Two things I want to point out though.

Looks like setTimeout would be more suitable here instead of setInterval, since it only calls the function passed into it once, and the code you wrote clears the interval after every rendering anyway. I checked my guess and it seems to work just exactly like your version but may look a bit more self-explanatory.

Besides, I have to mention, without explaining what the dependency array is the article looks incomplete.

Other than that, thanks a lot!

Won’t ‘else if (!isActive && seconds !== 0) {
}’ always be null, since logically it should be unset at that point. The usage of interval with a clear every second seems unncessary. Why not use ref and one interval?

I agree with Jack’s comment below – in your solution you clear the interval and run a new `setInterval` every second because of the `seconds` argument in the useEffect dependancy list.

Here’s a simpler implementation of the useEffect which doesn’t reset and start the setInterval again every second:

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

Full solution: https://codesandbox.io/s/eager-wright-wjy4h?file=/src/App.js