Peter Ekene Eze Learn, Apply, Share

Using forwardRef in React to clean up the DOM

8 min read 2333

Forwardref React DOM

Editor’s Note: This post was updated on 18 March 2021.

Introduction

In this tutorial, we will go over the concept of forwarding refs in React and understand how it helps us manage interactions with the DOM. For a more engaging experience, we’ll cover how to create refs, attach created refs to DOM elements and classes, use the forwardRef method, and so on.

It is also worthy to note that we will often make references to the docs page to build on the information that already exists and prove our concepts with relatable real-life examples and snippets to be hosted on CodeSandbox.

What is forwardRef in React?

React forwardRef is a method that allows parent components pass down (i.e., “forward”) refs to their children. Using forwardRef in React gives the child component a reference to a DOM element created by its parent component. This then allows the child to read and modify that element anywhere it is being used.

How does forwardRef work in React?

To understand ref forwarding, we must first understand what refs are, how they work, and cover a few use cases. Typically in React, parent components pass data down to their children via props.

To change the behavior of a child component you render it with a new set of props. To modify a child component such that it exhibits a slightly different behavior, we need a way to make this change without reaching for the state or re-rendering the component.

We can achieve this by using refs. With refs, we have access to a DOM node that is represented by an element. As a result, we can modify it without touching its state or re-rendering it.

Because refs hold a reference to the DOM element itself, we can manipulate it with native JavaScript functions that are unavailable in the React library. For instance, we can initiate focus on input field when a button is clicked:

import * as React from "react";
import ReactDOM from "react-dom";
 
export default function App() {
 const ref = React.useRef();
 
 function focus() {
   ref.current.focus();
 }
 
 return (
   <div className="App">
     <input ref={ref} placeholder="my input" />
     <button onClick={focus}>Focus</button>
   </div>
 );
}
 
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

You can also find the code at CodeSandbox.

Similarly, we could use JavaScript to achieve a similar effect, though it is not recommended, and even marked as a bad practice to access DOM directly when using React. The pure JavaScript equivalent to getting a reference would be:

We made a custom demo for .
No really. Click here to check it out.

document.getElementById('myInput).focus()

Through the ref, we manipulated our cursor to automatically focus on the input element whenever the button is clicked. Without refs, we’d have to use state to check if the input field should focus or not — that is before making a decision, which is often unnecessary in cases like this.

This information is important because forwardRef allows you to define internally what element the ref will point at.

When to use refs in React

There are many refs in React that can be pointed to using forwardRef. As seen in the official React documentation, we can use refs for a variety of tasks:

Managing focus, text selection, or media playback

Let’s imagine you have an input component. In some parts of your application, you may want the cursor focused on it when a user clicks a button. It makes more sense to modify only that particular instance of the input component without changing the state (via refs), rather than changing the state (via props) which will cause the component to re-render every-time. Similarly, we can use refs to control the state of music or video players (pause, play, stop) without them re-rendering anytime we click a button (change the state).

Incrementing values

Think about a Medium clap button. A quick way to implement a similar feature would be to increment the count value stored in the state every time a user clicks a clap. However, this may not be very efficient. Every time a user clicks the clap button, it will re-render, and, if we are sending a network request to store the value in a server, it will get sent as many times as the button is clicked. With refs, we can target that particular node and increment it every time a user clicks the button without causing a re-render and finally, we can send one request to our server with the final value.

By using this technique, we can increase the performance of our application as we prevent re-renders from happening, though it should only be used under particular circumstances and with care as it can lead to unexpected behaviors when used improperly.

Triggering imperative animations

We can use refs to trigger animation between elements that rely on themselves for their next state but exist in different components (this concept is called ref forwarding). Refs can also be used to simplify integration with third-party DOM libraries and managing multi-step form value states, etc.

Working with refs in class components

Creating refs in React

To create a ref, React provides a function called React.createRef(). Once created, they can be attached to React elements via the ref attribute. It is also worthy to note that refs are somewhat similar to state. When a component is constructed, refs get assigned to instance properties of that component ensuring that they can be referenced anywhere in the component:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.newRef = React.createRef(); //newRef is now available for use throughout our component
  }
 ...
}

At this point, we have created a Ref called newRef. To use this Ref in our component, we simply pass it as a value to the ref attribute like this:

class MyComponent extends React.Component {
 ...
  render() {
    return <div ref={this.myRef} />;
  }
}

We’ve attached the Ref here and passed in the newRef as it’s value. As a result, we now have the ability to update this without changing state. For additional info, learn more about how to use createRef in React.

Attaching refs

Refs are created when a component is rendered and can be defined either in the componentDidMount() or in the constructor(). As such, they can be attached to DOM elements or class components but cannot be attached to function components because they don’t have instances.

Every Ref you define will represent a node in the DOM. Hence, when you want to reference that node in a render() function, React provides a current attribute that references the said node.

const DOMNode = this.newRef.current; // refers to the node it represents

The value of the ref differs depending on the type of the node it references (class components or DOM elements).

For a better understanding of refs and type of node they reference, and the default values associated with each, let’s consider this piece from the documentation:

  • When the ref attribute is used on an HTML element, the ref created in the constructor with React.createRef() receives the underlying DOM element as its current property
  • When the ref attribute is used on a custom class component, the ref object receives the mounted instance of the component as its current i.e the components props, state, and methods

Let’s demonstrate this concept with a small video player. The video player will have some pause and play functionalities. To build along, create a new CodeSandbox project and add the following code:

import ReactDOM from "react-dom";
import React, { Component } from "react";

export default class App extends Component {
  constructor(props) {
    super(props);
    this.myVideo = React.createRef();
  }
  render() {
    return (
      <div>
        <video ref={this.myVideo} width="320" height="176" controls>
          <source
            src="https://res.cloudinary.com/daintu6ky/video/upload/v1573070866/Screen_Recording_2019-11-06_at_4.14.52_PM.mp4"
            type="video/mp4"
          />
        </video>
        <div>
          <button
            onClick={() => {
              this.myVideo.current.play();
            }}
          >
            Play
          </button>
          <button
            onClick={() => {
              this.myVideo.current.pause();
            }}
          >
            Pause
          </button>
        </div>
      </div>
    );
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

You can also find the code here.

Here, we used ref to pause and play our video player by calling the pause and play methods on the video. When the pause or play button is clicked, the function will be called on the video player without a re-render.

Using refs with function components

Refs cannot be attached to function components. Although, we can define refs and attach them to either DOM elements or class components. The bottom line is — function components do not have instances so you can’t reference them.

However, if you must attach a ref to a function component, the official React team recommends that you convert the component to a class, just like you would do when you need lifecycle methods or state.

Conditional refs

Aside from passing the default ref attribute, we can also pass functions to set refs. The major advantage of this approach is that you have more control over when refs are set and unset. That is possible because it gives us the ability to determine the state of the ref before certain actions are fired. Consider this snippet from the documentation page below:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = null;
    this.setTextInputRef = element => {
      this.textInput = element;
    };
    this.focusTextInput = () => {
      // Focus the text input using the raw DOM API
      if (this.textInput) this.textInput.focus();
    };
  }
  componentDidMount() {
    this.focusTextInput();
  }
  render() {
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

Instead of defining the refs in the constructor, we set the initial value to be null. The benefit of this approach is that textInput will not reference a node until the component is loaded (when the element is created).

Working with refs in function components

In function components, we can’t simply make use of the function createRef as the same because it will create a new reference each time it gets called. We could opt to use effects and state to handle references but React provides a much easier way with useRef. useRef takes care of returning the same ref each time as on the initial rendering.

Working with useRef is simple as we showed in the first example at the start of the tutorial.

Forwarding refs in React using forwardRef

When a child component needs to reference its parent component’s current node, the parent component needs a way to send down its ref to the child. The technique is called ref forwarding.

Ref forwarding is a technique for automatically passing a ref through a component to one of its children. It’s very useful when building reusable component libraries. forwardRef is a function used to pass the ref to a child component.

Let’s take an example of a new library with an InputText component that will provide a lot of functionality, though, for now, we’ll keep it simple:

const InputText = (props) => (
<input {...props} />
));

The InputText() component will tend to be used throughout the application in a similar manner as a regular DOM input, therefore accessing its DOM node may be unavoidable for managing focus, selection, or animations related to it.

In the example below, other components in the application have no access to the DOM input element generated by the InputText() component and is, thus, restricting some of the operations we have already foreseen we would need to meet our application requirements, such as controlling the focus of the input programmatically.

Here is when React.forwardRef enters to obtain a ref passed as props, and then forward it to the DOM input that it renders:

const InputText = React.forwardRef((props, ref) => (
 <input ref={ref} {...props} />
));

Now that our component supports forwardRef, let’s use it in the context of our application to build a button that will automatically focus the input when it’s clicked. The code looks as follows:

import * as React from "react";
import ReactDOM from "react-dom";
 
const InputText = React.forwardRef((props, ref) => (
 <input ref={ref} {...props} />
));
 
export default function App() {
 const ref = React.useRef();
 
 function focus() {
   ref.current.focus();
 }
 
 return (
   <div className="App">
     <InputText ref={ref} placeholder="my input" />
     <button onClick={focus}>Focus</button>
   </div>
 );
}
 
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

You can also see it in action on CodeSandBox.

Here’s a clarification for the code above:

  • We define a ref in the component that needs the ref and pass it to the button component
  • React will pass the ref through and forward it down to <input ref={ref}> by specifying it as a JSX attribute
  • When the ref is attached, ref.current will point to the <input> DOM node
  • The second ref argument in the InputRef component only exists when you define a component with React.forwardRef call
  • Regular function or class components don’t receive the ref argument, and ref is not available in props either
  • Ref forwarding is not limited to DOM components. You can forward refs to class component instances, too

Conclusion

Refs in React are a powerful tool that enable direct access to DOM nodes and thus open a whole new spectrum of methods and options to build more performant, feature-rich and clean components.

However, accessing DOM directly is often seen as a bad practice in React, and for a reason, when used improperly, it can turn all its benefits into real problems. As a general rule, it should be avoided and used only under very specific circumstances and with thorough examination.

In this tutorial, we introduced the topic of refs and ref forwarding, we looked at a few use cases and we built the code using function and classes components. To read more about refs check out the docs here.

Thanks for reading!

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Peter Ekene Eze Learn, Apply, Share

One Reply to “Using forwardRef in React to clean up the DOM”

  1. In general good information, but about forwardRef info, very disappointing. It is almost a copy/paste from the react documentation. I was looking to understand forwardRef better.

Leave a Reply