DEV Community 👩‍💻👨‍💻

Rohan Bagchi
Rohan Bagchi

Posted on • Originally published at rohanbagchi.hashnode.dev

Countdown timer with React

Countdown timer with React

Navigating the quirks of React's state and component model to build a countdown timer you can start, pause, resume, stop and reset. Idea is to start an interval and keep track of it's reference in a ref. On pausing said timer, we can clear the interval same way we can start a new timer on resume.


The ask goes like this:

Allow user to enter time in seconds. On click of Start, existing timer if any must be cancelled and it should begin countdown from the newly entered time.

To start with, lets split our work into 2 parts:

  1. Counting down seconds
  2. Formatting seconds into hh:mm:ss

Counting down seconds

Let's add a form with an input and a submit button.

<form onSubmit={handleStart}>
    <input
        type="number"
        placeholder="Enter total seconds to countdown"
        ref={inputRef}
    />

    <button type="submit">Start</button>
</form>
Enter fullscreen mode Exit fullscreen mode

This will give us a basic form with an uncontrolled field (which is fine as we do not really need a state on user enterred value).
There is also inputRef which is a vanilla React ref. Lets define it.

import { useRef } from "react";

export default function App() {
    const inputRef = useRef();

    return (
        <form onSubmit={handleStart}>
            <input
                type="number"
                placeholder="Enter total seconds to countdown"
                ref={inputRef}
            />

            <button type="submit">Start</button>
        </form>
    )
}
Enter fullscreen mode Exit fullscreen mode

Now lets consider the timer. The way it works is by decrementing a count until it reaches 0.
The only way to change a value and have it automatically updated in the DOM in React is by using state.

Let's add it below our inputRef declaration.

// ...
const inputRef = useRef();
const [currentTime, setCurrentTime] = useState(0);
// ...
Enter fullscreen mode Exit fullscreen mode

Next step is the actual countdown. We can make use of setInterval method to execute a decrement after every second. Along with this, we need to clear a running timer before the component unmounts.

Also, as per the ask, every time our form is submitted, we need to reset the timer. So we need reference to the timerID that persists accross re-renders without causing a re-render of it's own. To that effect, we can use a ref.

// ...
const inputRef = useRef();
const timerRef = useRef();
const [currentTime, setCurrentTime] = useState(0);

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

const startTimer = () => {
    timerRef.current = setInterval(() => {
        setCurrentTime((prev) => prev - 1);
    }, 1000);
}; 
// ...
Enter fullscreen mode Exit fullscreen mode

The useEffect here returns a cleanup function that executes on unmount. Here we are clearing up any running timer.

The startTimer function is responsible for starting a timer with a fixed delay of 1000 milliseconds and decrementing currentTime by 1 on every tick.

Let's bring it all together in our handleStart function which get's triggered on form submit.

Things to note:

  1. handleStart will clear any existing timers
  2. set value of currentTime
  3. trigger startTimer
// ...
const handleStart = e => {
    e.preventDefault();

    if (timerRef.current) {
      clearInterval(timerRef.current);
    }

    const secondsInput = inputRef.current.value;

    setCurrentTime(() => secondsInput);

    startTimer();
};
// ...
Enter fullscreen mode Exit fullscreen mode

Here, const secondsInput = inputRef.current.value; gets the current value of the input.

Formatting seconds into hh:mm:ss

Now that we have a decrementing counter, lets also add a utility to format it to hh:mm:ss.

const secondsToHHMMSS = (seconds) => {
  if (seconds < 3600)
    return new Date(seconds * 1000).toISOString().substr(14, 5);

  return new Date(seconds * 1000).toISOString().substr(11, 8);
};
Enter fullscreen mode Exit fullscreen mode

^ is picked from this helpful stackoverflow answer: https://stackoverflow.com/a/1322771/1939344

Now, lets render it below the form:

// ...
return (
    <div className="App">
        <form onSubmit={handleStart}>
            <input
            type="number"
            placeholder="Enter total seconds to countdown"
            ref={inputRef}
            />

            <button type="submit">Start</button>
        </form>

        <div className="time">{secondsToHHMMSS(currentTime)}</div>
    </div>
);
// ...
Enter fullscreen mode Exit fullscreen mode

Finished result:

Top comments (0)

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

⭐️🎀 JavaScript Visualized: Promises & Async/Await

async await