How to build a Stopwatch in React
Sagar Sharma
Posted on July 23, 2024
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
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.
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>
</>
)
}
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>
</>
)
}
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);
}
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);
}
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.
Next, our stop function will only work if the stopwatch is running.
const stop = () => {
if (isRunning) {
clearInterval(intervalID);
setIsRunning(false);
}
}
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);
}
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
If we change any state variable, the whole component re-renders, but if we update any reference variable, there is no re-render.
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.
- Import the useRef Hook
- Initialize the variable
const intervalID = useRef(null);
Initially, we are giving it a null (empty) value instead of 0.
- .current Property
Now, this IntervalID is an object which only has one property - .current
{
current: value //null in our case
}
To update or change the value, we have to use .current property.
- Updating the Start Function
const start = () => {
if (!isRunning) {
intervalID.current = setInterval(() => {
setCurrentTime((prevTime) => {
return prevTime + 10;
})
}, 10)
}
setIsRunning(true);
}
- Updating the Stop Function
const stop = () => {
if (isRunning) {
clearInterval(intervalID.current);
setIsRunning(false);
}
}
- Updating the Reset Function
const reset = () => {
clearInterval(intervalID.current);
setCurrentTime(0);
setIsRunning(false);
}
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);
}, []);
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>
</>
)
}
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!
Posted on July 23, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.