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.

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
James Dietrich is an experienced web developer, educator, and founder of Upmostly.com, a platform offering JavaScript-focused web development tutorials. He's passionate about teaching and inspiring developers, with tutorials covering both frontend and backend development. In his free time, James contributes to the open-source community and champions collaboration for the growth of the web development ecosystem.

💬 Leave a comment

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

We will never share your email with anyone else.