Creating a Simple Countdown Timer Using React useRef Hook
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!
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 rundecreaseNum
function every 1000 ms or 1 second.decreaseNum
simply updates thenum
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:
Conclusion
Hope this brief tutorial demonstrates the usefulness of useRef
. For additional information on this hook, check out the official React documentation.