Create a basic stopwatch in React that lets the user start, pause and clear.
A basic UI is fine with inline styles. It should handle hours, minutes and seconds.
Example
01:10:02
This question was asked in xAI (Twitter) for the Frontend Engineering Specialist position to me.
Simple question, but evaluates your understanding of the React fundamentals.
Because I had to solve this problem within 30 minutes, I broke the problem statement into two parts and used inline styling to save time.
- Handle the Start, Pause, Resume, and Reset state.
- Format the count to display it in the format
HH:MM:SS
Handle the Start, Pause, Resume, and Reset state
We will maintain two different states, one will track the count of time passed, that will be auto incremented every second and the second will control the start, pause, resume actions.
The time auto updated will be controlled using a timer ref.
const [startedTimer, setStartedTimer] = useState(false); const [currentTime, setCurrentTime] = useState(0); const timerRef = useRef(null);
I have created two buttons for this purpose, the first will handle the Start, Pause, Resume actions, this will be controlled using both the states, where if the currentTime is having value, then we resume the action other wise we will start from beginning. The startedTimer state helps to pause the running timer.
The second button is the reset, that will reset the state.
The state change will be listened by useEffect() hook under which we will start or stop the timer.
useEffect(() => {
if (startedTimer) {
timerRef.current = setTimeout(() => {
setCurrentTime(currentTime + 1000);
}, 1000);
}
return () => {
clearTimeout(timerRef.current);
};
}, [currentTime, setCurrentTime, startedTimer]);
const handleStartTimer = () => {
setStartedTimer((prev) => !prev);
};
const reset = () => {
setStartedTimer(false);
setCurrentTime(0);
clearTimeout(timerRef.current);
};
return (
<div
style={{
width: "100vw",
height: "100vh",
background: "white",
padding: 20,
textAlign: "center",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "10px",
}}
>
<button onClick={handleStartTimer}>
{startedTimer ? "Pause" : currentTime > 0 ? "Resume" : "Start"}
</button>
<button onClick={reset}>Reset</button>
</div>
</div>
);
Format the count to display it in the format HH:MM:SS
To format the time I have created a helper function and did some basic calculation.
As the timer count is increase by 1000 each second, I have used this to denote the millisecond increase, this makes it easier to convert it using the basic calculations.
For example:
- A second = milliseconds / 1000
- A minute = seconds / 60
- An hour = minute / 60
Using this simple calculation and handling additional check like adding a padded 0 when the values are in single digit and resetting the value if we have crossed 60 did the work.
const getFormattedTime = (time) => {
const getPaddedValues = (val) => {
return !isNaN(val)
? val < 10
? `0${Math.floor(val)}`
: Math.floor(val)
: `00`;
};
const seconds = time / 1000;
const secondsResetted = seconds % 60;
const minutes = seconds / 60;
const minutesResetted = minutes % 60;
const hours = minutes / 60;
const secondsPadded = getPaddedValues(secondsResetted);
const minutesPadded = getPaddedValues(minutesResetted);
const hoursPadded = getPaddedValues(hours);
return `${hoursPadded}:${minutesPadded}:${secondsPadded}`;
};
Render the formatted value.
<div style={{ margin: "10px 0 0 0" }}>
{getFormattedTime(currentTime)}
</div>
Complete code
import React, { useRef, useState, useEffect } from "react";
function App() {
const [startedTimer, setStartedTimer] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const timerRef = useRef(null);
useEffect(() => {
if (startedTimer) {
timerRef.current = setTimeout(() => {
setCurrentTime(currentTime + 1000);
}, 1000);
}
return () => {
clearTimeout(timerRef.current);
};
}, [currentTime, setCurrentTime, startedTimer]);
const handleStartTimer = () => {
setStartedTimer((prev) => !prev);
};
const reset = () => {
setStartedTimer(false);
setCurrentTime(0);
clearTimeout(timerRef.current);
};
const getFormattedTime = (time) => {
const getPaddedValues = (val) => {
return !isNaN(val)
? val < 10
? `0${Math.floor(val)}`
: Math.floor(val)
: `00`;
};
const seconds = time / 1000;
const secondsResetted = seconds % 60;
const minutes = seconds / 60;
const minutesResetted = minutes % 60;
const hours = minutes / 60;
const secondsPadded = getPaddedValues(secondsResetted);
const minutesPadded = getPaddedValues(minutesResetted);
const hoursPadded = getPaddedValues(hours);
return `${hoursPadded}:${minutesPadded}:${secondsPadded}`;
};
return (
<div
style={{
width: "100vw",
height: "100vh",
background: "white",
padding: 20,
textAlign: "center",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "10px",
}}
>
<button onClick={handleStartTimer}>
{startedTimer ? "Pause" : currentTime > 0 ? "Resume" : "Start"}
</button>
<button onClick={reset}>Reset</button>
</div>
<div style={{ margin: "10px 0 0 0" }}>
{getFormattedTime(currentTime)}
</div>
</div>
);
}
export default App;
Output:
Stopwatch reset state

Stopwatch paused state

Stopwatch resumed state

Conclusion
Stopwatch are the very basic interview questions but it is easy to get stuck at the resuming and pausing the timer and formatting the values, thus practice accordingly.