- Understand common use cases for the
useRef
hook - Use the
useRef
hook to access DOM elements - Use the
useRef
hook to persist data across multiple component renders
In this lesson, we'll explore how to use the useRef
hook and some common use
cases for it. You can find starter code with the examples we'll discuss in the
src/components
directory. Run npm install && npm start
to run the example
code and code along!
The useRef
hook gives us a way to capture a reference to values that are
accessible across multiple renders of our component. In some ways, it's similar
to the useState
hook: the useState
hook also lets us keep track of values
across multiple renders of our component, like this:
import React, { useState } from "react";
function CounterState() {
const [count, setCount] = useState(0);
function handleClick() {
setCount((count) => count + 1);
}
return (
<div>
<h1>CounterState</h1>
<button onClick={handleClick}>{count}</button>
</div>
);
}
In this example, we use the useState
hook to create some new internal state
within React that we can access using the count
variable every time our
component re-renders. Also, calling setCount
will trigger a re-render.
Using a ref instead, our component would look like this:
import React, { useRef } from "react";
function CounterRef() {
const count = useRef(0);
function handleClick() {
count.current = count.current + 1;
console.log(count.current);
}
return (
<div>
<h1>CounterRef</h1>
<button onClick={handleClick}>{count.current}</button>
</div>
);
}
To break down the code:
- We must first import the
useRef
hook, just like with the other hooks we've seen - We call
useRef
and pass in an initial value - Calling
useRef
creates a new internal value in React and gives us access to that value in a ref variable, which is an object with just one key:current
. It looks like this:{ current: 0 }
- To update the value of the ref in React's internals, we update its
current
property:count.current = count.current + 1
The key difference between these approaches is that in the useRef
example,
updating the ref variable does not cause our component to re-render. Try out
both buttons in the browser to see the difference.
useRef
still allows us to have a variable that persists between renders of our
component, but since updating its value doesn't trigger a re-render, we use it
in different situations than when we'd use useState
. You can think of this ref
variable almost like an instance variable for your function components.
Let's see some good use cases for the useRef
hook.
Let's build out a price tracking component. The features of this component are:
- Every 1 second, generate a new random price
- If the old price is less than the new price, use a green font color to indicate a rise in price
- If the old price is greater than the new price, use a red font color to indicate a drop in price
Here's some starter code that implements the first feature of generating a random price each second:
import React, { useEffect, useState } from "react";
import { makeRandomNumber } from "../utils";
function Ticker() {
const [price, setPrice] = useState(0);
const [color, setColor] = useState("black");
useEffect(() => {
// every 1 second, generate a new random price
const id = setInterval(() => setPrice(makeRandomNumber), 1000);
return function () {
clearInterval(id);
};
}, []);
return (
<div>
<h1>TickerMaster</h1>
<h2 style={{ color: color }}>Price: ${price}</h2>
</div>
);
}
What we want is a way to set the color based on the change in price between the previous render and the current render. Since we want to change the color based on the price, we can start off by writing out a side effect with the price as the dependency:
useEffect(() => {
// we need some way to get the prevPrice...
if (price > prevPrice) {
setColor("green");
} else if (price < prevPrice) {
setColor("red");
} else {
setColor("black");
}
}, [price]);
To make this work, we need to persist the previous price. This is where we can
use the useRef
hook! Since our goal is to:
- Access the same data across renders
- Not re-render the component when saving this data
useRef
is a good tool for the job of storing the previous price. Here's how
we'd use it:
import React, { useEffect, useRef, useState } from "react";
import { makeRandomNumber } from "../utils";
function Ticker() {
const [price, setPrice] = useState(0);
const [color, setColor] = useState("black");
// create the ref and set its initial value
const prevPriceRef = useRef(price);
useEffect(() => {
// use the current value of the ref
const prevPrice = prevPriceRef.current;
if (price > prevPrice) {
setColor("green");
} else if (price < prevPrice) {
setColor("red");
} else {
setColor("black");
}
// set the new value of the ref (note: this doesn't trigger a re-render)
prevPriceRef.current = price;
}, [price]);
useEffect(() => {
const id = setInterval(() => setPrice(makeRandomNumber), 1000);
return function cleanup() {
clearInterval(id);
};
}, []);
return (
<div>
<h1>TickerMaster</h1>
<h2 style={{ color: color }}>Price: ${price}</h2>
</div>
);
}
Try using this component in the browser. Explore the component's render cycle by adding some console messages and see how the values in state and in the ref change over time.
Another common use case for the useRef
hook is to gain access to the actual
DOM elements being created by our React components. In general, we want to give
React control over the DOM based on the JSX that is returned by our components.
However, sometimes it is also useful to gain access to the actual DOM elements
for a few uses outside of the React rendering cycle, such as:
- using a third-party library that needs access to a DOM element
- accessing input values in a non-controlled form
- setting focus on an element
- measuring the size of a DOM element
- working with a
<canvas>
or<video>
element
To use a ref on a DOM element, we first create the ref using the useRef
hook,
just like before:
function Box() {
const elementRef = useRef();
return (
<div>
<h1>Box</h1>
<button>Measure</button>
</div>
);
}
Then, we can attach the ref to a DOM element by adding a special ref
attribute
to our JSX element:
function Box() {
const elementRef = useRef();
return (
<div ref={elementRef}>
<h1>Box</h1>
<button>Measure</button>
</div>
);
}
Now, we can access information about that DOM element in our component:
function Box() {
const elementRef = useRef();
function handleMeasureClick() {
const div = elementRef.current;
console.log("Measurements: ", div.getBoundingClientRect());
}
return (
<div ref={elementRef}>
<h1>Box</h1>
<button onClick={handleMeasureClick}>Measure</button>
</div>
);
}
Another example of using the useRef
hook to access a DOM element is in the
TickerChart
component included in the src/components
directory. In that
example, we're using a <canvas>
element to draw out a graph of the price
changes over time.
Like useState
, the useRef
hook gives us some internal React values that
will persist across renders of our component. Unlike state, when we update a
ref, React will not automatically re-render our component. This makes refs
useful for keeping track of persistent data in our components, similar to an
instance variable.
A ref variable can also be used to gain access to DOM elements using the
ref
prop on a JSX element.