DEV Community

Sagar Sharma
Sagar Sharma

Posted on

How to build a Stopwatch in React

Hi there, Let's understand how a stopwatch is built in React. This is a beginners guide.

Build basic Setup

I am using vite for creating react project as its fast and efficient.

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

Now, in your project folder, create a new component Stopwatch.jsx. It's a simple project so we don't need to divide our components much.

stopwatch component

Now, move into your component file and write the basic setup. We will need three things now - element to show time, start button, stop button.

export default function Stopwatch() {
    return (
        <>
            <h2>Stopwatch count</h2>
            <button>Start</button>
            <button>Stop</button>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Setting State to track current time

We have to set state to track the elapsed time (eg- in seconds) and a boolean state to track if the stopwatch is running or not.

import { useState } from "react"

export default function Stopwatch() {

    const [current, setCurrentTime] = useState(0);
    const [isRunning, setIsRunning] = useState(false);

    return (
        <>
            <h2>Stopwatch count</h2>
            <button>Start</button>
            <button>Stop</button>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Starting the Stopwatch

Our main functions will be Start, Stop and Reset. Let's first code the start function.

We will add onClick function to the start button.

<button onClick={start}>Start</button>

Next, let's create the start function, now we want this function to only work when the stopwatch is off/ not running.

const start = () => {

        if (!isRunning) {

        }

        setIsRunning(true);
    }
Enter fullscreen mode Exit fullscreen mode

We will set the stopwatch running after the timer has started. Now, the main function is to increment the time in every specific time. We are incrementing in every 10 milliseconds in this stopwatch.

let intervalID = 0;  //we are storing the setInterval() to later clear it while stopping

const start = () => {

        if (!isRunning) {
            intervalID = setInterval(() => {
                setCurrentTime((prevTime) => {
                    return prevTime + 10;
                })
            }, 10)
        }

        setIsRunning(true);
    }
Enter fullscreen mode Exit fullscreen mode

In every 10 ms, we are updating the currentTime state. ( You can also update in every 1000ms i.e - 1 second ).

Stopping the Stopwatch

Similar to above start function, we will add a stop function to an onClick function on Stop button.

stopping button

Next, our stop function will only work if the stopwatch is running.

const stop = () => {
        if (isRunning) {
            clearInterval(intervalID);
            setIsRunning(false);
        }

    }
Enter fullscreen mode Exit fullscreen mode

In the above code, we are also setting the isRunning to false, why? because in the start function, we have mentioned the condition that it will only work if stopwatch is not running.

Reset the Stopwatch

It's similar to stop function, the only difference is that here we will set the current time to 0 to render the reset of stopwatch.

const reset = () => {
        clearInterval(intervalID);
        setCurrentTime(0);
        setIsRunning(false);
    }
Enter fullscreen mode Exit fullscreen mode

But, there is a problem

Great, the stopwatch is complete 90%! But, there is an underlying problem with the above code.

We are storing the intervalID in local variable. The issue with local variables is that they don't survive re-renders. What I mean by that is if our component re-renders due to any state update in future then the local variables are reinitialized.

IntervalID will be reinitialized everytime the state changes and component re-renders and react wouldn't keep reference to stop when we try to clearInterval.

So, if we want our component to handle complex states in future without losing the reference to IntervalID, we have to store it in reference variable. Here comes the useRef Hook.

useRef Hook

In simple language, its a secret pocket which react can't track. In re-renders, the value stored in reference variable will not be lost or reset. It's similar to useState but there is a key difference.

Difference btw useRef and useState

  1. If we change any state variable, the whole component re-renders, but if we update any reference variable, there is no re-render.

  2. useRef is used rarely where the data is not needed for rendering. (For e.g- our IntervalID which is a timeout function). Use useState when you want to display that data in component and update it. (For e.g- counter button)

To know more in-depth use cases for useRef, checkout Link

Implementing useRef in our Stopwatch

Let's focus back on our Stopwatch, we just have to store the IntervalID in reference variable with the help of useRef Hook.

  1. Import the useRef Hook

importing useRef hook

  1. Initialize the variable

const intervalID = useRef(null);

Initially, we are giving it a null (empty) value instead of 0.

  1. .current Property

Now, this IntervalID is an object which only has one property - .current

{
        current: value //null in our case
    }
Enter fullscreen mode Exit fullscreen mode

To update or change the value, we have to use .current property.

  1. Updating the Start Function
const start = () => {

        if (!isRunning) {
            intervalID.current = setInterval(() => {
                setCurrentTime((prevTime) => {
                    return prevTime + 10;
                })
            }, 10)
        }

        setIsRunning(true);
    }
Enter fullscreen mode Exit fullscreen mode
  1. Updating the Stop Function
const stop = () => {
        if (isRunning) {
            clearInterval(intervalID.current);
            setIsRunning(false);
        }

    }
Enter fullscreen mode Exit fullscreen mode
  1. Updating the Reset Function
const reset = () => {
        clearInterval(intervalID.current);
        setCurrentTime(0);
        setIsRunning(false);
    }
Enter fullscreen mode Exit fullscreen mode

And its done! Our Simple Stopwatch is built! Give a self-pat on your back and let's do the final touches.

useEffect to clean up any remaining IntervalID

We don't want that if we start our program there is some residual leftover IntervalID, so we can use a useEffect Hook that will only take effect on mounting the component.

useEffect(() => {
        return () => clearInterval(intervalID.current);
    }, []);

Enter fullscreen mode Exit fullscreen mode

Source Code

import { useState, useRef, useEffect } from "react"

export default function Stopwatch() {

    const [currentTime, setCurrentTime] = useState(0);
    const [isRunning, setIsRunning] = useState(false);

    const intervalID = useRef(null);



    const start = () => {

        if (!isRunning) {
            intervalID.current = setInterval(() => {
                setCurrentTime((prevTime) => {
                    return prevTime + 10;
                })
            }, 10)
        }

        setIsRunning(true);
    }




    const stop = () => {
        if (isRunning) {
            clearInterval(intervalID.current);
            setIsRunning(false);
        }

    }

    const reset = () => {
        clearInterval(intervalID.current);
        setCurrentTime(0);
        setIsRunning(false);
    }

    useEffect(() => {
        return () => clearInterval(intervalID.current);
    }, []);


    return (
        <>
            <h2>{(currentTime / 1000).toFixed(2)}</h2>
            <button onClick={start}>Start</button>
            <button onClick={stop}>Stop</button>
            <button onClick={reset}>Reset</button>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Thanks for reading till here, we can improve this stopwatch to make it more fancy but we are not gonna cover that in this blog. This is a beginner guide to understand the internal working.

All the best on your Coding Journey!

Top comments (0)