Rachelle Rathbone

Noob Tidbit #6: Refs

What They Are and How to Use Them

November 04, 2020

Refs were one of those things that evaded my understanding completely in my first year or so of using React. I hadn't run into a real use case for them and had heard conflicting information about whether you even should use them. I had also talked to developers who were several years into their journey who had never used them and didn't really see a need for them. However refs, commonly referred to as an 'escape hatch', were created for a reason and can prove to be a useful tool in your tool kit once you understand what they are and how you can use them.

What is a Ref and How Can They Help Us?

A ref is an attribute that is used either on an HTML element or a component tag, providing us with a way to get direct access to a DOM element or an instance of a component.

Example of accessing an instance of a component:

In this example, adding the ref attribute to the EmailInput will allow us to access all of the functions and properties of the EmailInput component (this only works in EmailInput is a class component... but more on that later).

Example of accessing a DOM element:

In React there are different versions of ref:
  • callback refs: used in older versions of React, callback refs pass a function which receives an HTML DOM element or component instance as its argument.
  • createRef: seen in the above screenshots, createRef was introduced in React 16.3 and returns a new ref on every render.
  • useRef hook: returns the same ref each time.

All versions return a mutable object that lasts for the entire lifecycle of the component. This object has a .current property which is initialized the the initialValue argument that gets passed in. When I click on the div element in the last example I provided, where I'm logging console.log(this.elementRef) from within the handleClicking handler, I see the following in the console:

We can see the current object which provides us access to all the methods and properties on the clicked div element (and that list is looooooooong).

How to Use Refs

Just like in the screenshots seen earlier, you need to add the ref attribute to your component/element whose value is a callback function. This function will receive the underlying DOM element or the mounted instance of the component as its first argument.

<div ref={this.elementRef} onClick={this.handleClicking}>An element</div>

If you were to do this, you would then be able to access the properties and methods on the .current object and do with them as you please. However, without a practical use case, this doesn't seem entirely useful so let's look at example where adding a ref really can come in handy.

Let's imagine you have a simple input field on a page.

When a user lands on that page, in order to be able to type in the input field, they have to manually click into the input so that it will focus and they can type.

While this isn't a huge annoyance, it certainly is nice when you go to a page and start filling in a form without having to move your hands away from the keyboard. Thankfully, this functionality can be achieved very easily with refs.

In this example we start by creating a ref with the React.useRef hook, setting its initial state to null. Then, we utilize React's useEffect method to call the focus method on the DOM element's .current object. Finally, we add the ref attribute to our input element and pass in inputRef.

Now, when the page loads, the input is automatically focused and the user can start typing without having to manually click into it.

But what if we need to define a ref in a parent and pass it down to a child? Well, there are 2 ways to do this:

Pass the ref from a class component to another class component: as mentioned in the very first example with EmailInput, the child component also needs to be a class in order for the parent to be able to access the properties and methods.

Below we can see a class component where we create a ref using React.createRef() and pass it to the ref attribute on the EmailInput component.

The EmailInput component is a class component where we have currently defined one method called focusEmailInput.

And now if we check the console, we can see the current object which is being logged from componentDidMount in the parent component. We can now access all the properties and methods on the child component, EmailInput.

If we try to do this when the child component is a functional component, we will no longer be able to access the instance of our EmailInput component.

Above is the EmailInput component converted from a class component to a functional component. And below is now what we see in the console from the log in componentDidMount on our parent component.

But what if you have a child component that is a functional component and later on decide you need to pass down a ref? You could refactor it to be a class component but this isn't very practical. This brings us to the second way we can pass refs from parent to change.

Pass the ref using React.forwardRef: this allows us to pass a ref from a class component to a functional component, or from a functional component to another functional component.

Let's make a small change to our log from within componentDidMount in the parent component:

And convert the EmailInput component to a functional component where we use React.forwardRef and pass in the ref argument (which will be the second argument).

When we check the logs we can see that we now once again have access to the properties and functions of EmailInput even though it is no longer a class component.

Hopefully you now have a better understanding of what refs are and how to use them. One last thing to keep in mind before you run off and start adding refs everywhere in your code: refs should be used sparingly and only as a last resort.

React embraces a declarative philosophy and in the React documentation they encourage developers to implement functionality in a declarative way wherever possible. In the event that something you're trying to implement cannot be done declaratively, then you can try an imperative approach. Our ref 'escape hatch' falls into this bucket. Use it wisely and only when absolutely necessary to avoid breaking design patterns.
© 2023, Rachelle Rathbone