Creating a Simple Countdown Timer Using React useRef Hook

Anh
The Startup
Published in
4 min readDec 11, 2020
Photo by Lukas Blazek on Unsplash

In React, useRef is a hook that…

returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component. (Source)

It is normally used to reference underlying DOM elements, an input field for example. However, you can also use it to store reference to any variable. In this way, it may seem no different than useState, but what distinguishes it from useState is that when we update a useRef reference, it does not cause the component to re-render. Simply put, you use it to keep track of a variable, but this variable does not directly impact what is displayed on the DOM (and therefore re-rendering of the component is unnecessary).

As defined by React above, useRef gives you an object with the .current property. Below is a simple example of how it is used:

let count = useRef(1);

To later retrieve the value of 1, we can call something like: console.log(count.current).

To better demonstrate a practical use of useRef, let’s create a simple countdown timer. Our goal is to have a countdown timer that will automatically start counting down from 100 when the page loads. We’ll also have a button that allows for the pausing and resuming of the timer. Let’s get to it!

Countdown Timer

0. Set Up An App Skeleton

We’ll bootstrap our simple app using create-react-app. In the terminal, run: npx create-react-app countdown-timer. Wait for dependencies to be installed, then cd into the created directory.

We’ll then create a directory within src called components, and create a file called Countdown.js. We will then import Countdown.js into App.js:

In the Countdown.js file, we will import useRef, useState, and useEffect from React. We’ll also set up the functional component skeleton like the below:

1. Set Up States and Ref

Next, we’ll use useState to initialize the num state to 100 since we’ll start our countdown from 100. We’ll have a pause state initialized to false:

const [num, setNum] = useState(100);
const [pause, setPause] = useState(false);

We will also create an intervalRef, which we will later use to store a reference to our interval.

let intervalRef = useRef();

We would need a way to refer to the interval because we will have to clear it in several different places in our code. For a quick refresher on setInterval and clearInterval, both window methods which we will use, click here.

We will also add the following to our return statement of the component to display our number, followed by a button that will display either “Run” or “Pause” depending on the value of our pause state:

return (
<div>
<div>{num}</div>
<button>{pause ? "Run" : "Pause"}</button>
</div>
);

So far we have:

2. Set Up useEffect

Next, we’ll add the following to our component:

const decreaseNum = () => setNum((prev) => prev - 1);useEffect(() => {
intervalRef.current = setInterval(decreaseNum, 1000);
return () => clearInterval(intervalRef.current);
}, []);

Relying on the useEffect hook, when we are telling React to do the following when the component mounts:

  • use setInterval() to start an interval that will run decreaseNum function every 1000 ms or 1 second. decreaseNum simply updates the num state to 1 less than the previous value
  • store the reference to the interval we just set in intervalRef.current

The function that follows the return statement in useEffect can be used to run whatever cleanup activity needed when the component un-mounts. So here, we specified that we want the interval to clear or stop running, passing in intervalRef.current. Finally, the array that is the second argument to that useEffect stores any dependencies that exist in useEffect that should cause useEffect to run again. In our case, we have none, so using an empty array here will simply have useEffect run once when the component mounts.

Our code so far:

3. Handle Button Click

Next, we’ll write a function to handle the button click, add the following event listener to the button element: onClick={handleClick}. Then we’ll write our handleClick function:

const handleClick = () => {
if (!pause) {
clearInterval(intervalRef.current);
} else {
intervalRef.current = setInterval(decreaseNum, 1000);
}
setPause((prev) => !prev);
};

Here, we specify that if currently, pause is false (meaning that our timer is currently running), we should clear the interval. Again, we have access to the interval reference that we previously assigned a value to in the useEffect hook, and we can pass in that reference to clearInterval. Otherwise if the timer is paused, we should start the interval again. Here we also update intervalRef.current to the new interval started. Finally, we toggle the state of pause.

What the whole component looks like in code:

And the finished product:

Countdown Timer

Conclusion

Hope this brief tutorial demonstrates the usefulness of useRef. For additional information on this hook, check out the official React documentation.

--

--