What is React Refs

By default, React will be handling all the DOM manipulation automatically, and you only need to describe what the UI should look like. But in some cases, you might need to access and manipulate the DOM manually, such as accessing the size of an element, implementing drag and drop, managing scroll positions, and so on.

Refs, short for references, are used to directly access and manipulate DOM elements or components without using the standard React state and props system.

Declaring refs

Refs are created with the useRef() hook, which can be imported like this:

jsx
1import { useRef } from "react";

Inside your component, declare a ref like this:

jsx
1export default function App() {
2  const divRef = useRef(null);
3
4  return (. . .);
5}

useRef() returns an object with a current property, which is supposed to point to a DOM node. But for now, we are initializing it to be null.

Tieing refs to DOM nodes

You can then assign a DOM node to our divRef.current by specifying a ref attribute.

jsx
1import { useRef } from "react";
2
3export default function App() {
4  const divRef = useRef(null);
5
6  return <div ref={divRef}>Lorem ipsum dolor. . .</div>;
7}

This will tell React to tie this <div> element to divRef.current. You can now access the associated DOM element and its properties directly via the standard web APIs like this:

jsx
1import { useRef, useState } from "react";
2
3export default function App() {
4  const divRef = useRef(null);
5  const [height, setHeight] = useState(0);
6
7  return (
8    <>
9      <div ref={divRef}>Lorem ipsum dolor. . .</div>
10
11      <button
12        onClick={() => {
13          setHeight(divRef.current.clientHeight);
14        }}>
15        Calculate Height
16      </button>
17
18      <div>{height}</div>
19    </>
20  );
21}

In this example, clientHeight returns the height of an element. When you click on the button, the corresponding event handler will update height with divRef.current.clientHeight.

Refs are attached after rendering

One important thing to note about refs is that React only attaches the DOM node after the DOM tree has been rendered, which means when you try to access the ref before or during the rendering phase, that ref will be assigned null, and will likely cause an error in your code. For instance,

jsx
1import { useRef } from "react";
2
3export default function App() {
4  const divRef = useRef(null);
5
6  console.log(divRef.current.clientHeight); // This line will cause an error
7
8  return <div ref={divRef}>Lorem ipsum dolor. . .</div>;
9}

In this example, we are trying to access clientHeight before the <div> element is rendered. divRef.current will be assigned null at the time, so an error will be returned.

Ref error

This is why in most cases, you will be dealing with refs inside event handlers, which are also only attached after rendering. If you need to do something with ref, but there is no associated event handler, you will need to use side effects, which we are going to discuss in the next lesson.

Best practices for refs

Refs is a way to step outside of scope of React, giving you direct access to the DOM tree. It can be a powerful tool for interacting with the DOM, but it is essential for you to understand its proper role in a React application, and use it with caution.

When using refs, there are several best practice guidelines you should follow.

First of all, do not rely on refs. One of React’s core strengths is its declarative nature, where you can describe what the UI should look like, and React takes care of DOM manipulation for you. Excessive use of refs can break this declarative model and make your code more difficult to manage and debug.

And second, you should only access refs after the component has been rendered. As discussed above, React only attaches the DOM node to the corresponding ref after the component has been rendered. Attempting to access a ref before that will return null. To be safe, you should place ref related logic inside event handlers or side effects.

Lastly, although refs can store data across renders like like states, you should avoid using refs for state management. Consider the following example,

jsx
1import { useRef } from "react";
2
3export default function App() {
4  const count = useRef(0);
5
6  return (
7    <div>
8      <p>{count.current}</p>
9      <button
10        onClick={() => {
11          count.current = count.current + 1;
12          console.log(count.current);
13        }}>
14        Add
15      </button>
16    </div>
17  );
18}

Every time the button is clicked, count.current will increment by 1, as you can see logged in the console. However, notice that the counter displayed on the webpage will not be updated because changes to count.current do not trigger re-render.