Using refs in React

One of the reasons why React is so popular is the declarative way you build apps in React. React is built from components and as a developer it's your responsibility to composing components to described the DOM (Document Object Model). React provides an abstraction layer from the DOM so you can focus on building your app.

This concept is very powerful, because in the typical React data flow, props are the only way that parent components interact with their children. To modify a child, you re-render it with new props. However, there are a few cases where you need to imperatively modify a child outside of the typical data flow. The child to be modified could be an instance of a React component, or it could be a DOM element. For both of these cases, React provides an escape hatch called Refs.

#Using refs in React When should we use Ref's? In the documentation of React, Facebook names some use cases such as:

  • Managing focus, text selection, or media playback.
  • Triggering imperative animations.
  • Integrating with third-party DOM libraries.

I will publish a new article soon using IntersectionObserver API and Refs, etc to demonstrate imperative animation.

There are technically three ways to create refs, but only two recommend ways. The first method described below is the deprecated string method. You pass in a string to ref in the JSX and the ref is saved in this.refs under that string like this:

If you are new to React, remember that the first initial render happens before the lifecycle method componentDidMount.

import React, { Component } from 'react';

class LoginForm extends React.Component {
  componentDidMount() {
    this.refs.usernameInput.focus();  }
  render() {
    return (
      <form>
        Username: <input ref="usernameInput" />        Password: <input />
      </form>
    );
  }
}

export default LoginForm;

The next way to create refs is to use the callback method. Instead of passing a string, you pass a callback function and on render, it will pass the ref as an argument to the callback. From there you can do things with the ref including saving it for later use:

import React, { Component } from 'react';

class LoginForm extends React.Component {
  constructor(props) {
    super(props);
  }

  // Class property holding the ref  
  usernameInput = null;
  componentDidMount() {
    this.usernameInput.focus();
  }

  // Callback function - saving the ref 
  handleRef = (ref) => {
    this.usernameInput = ref;  }

  render() {
    return (
      <form>
        Username: <input ref={this.handleRef} />        Password: <input />
      </form>
    );
 }
}

export default LoginForm;

We are no longer using this.refs to access the usernameInput, instead we are using the callback which lets us save it on a property in the class. From there we can access the DOM Node directly from the usernameInput property on the class itself.

Note: If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element. This is because a new instance of the function is created with each render, so React needs to clear the old ref and set up the new one. You can avoid this by defining the ref callback as a bound method on the class.

The final way was added to React 16.3.0 and that means you can use React.createRef() and pass it that to a ref like this:

import React, { Component } from 'react';

class LoginForm extends React.Component {
  constructor(props) {
    super(props);

    // Create the ref in the constructor
    this.usernameInput = React.createRef();  }
  
  componentDidMount() {
    this.usernameInput.current.focus();
  }
  
  render() {
    return (
      <form>
        Username: <input ref={this.usernameInput} />        Password: <input />
      </form>
    );
  }
}

export default LoginForm;

This method eliminates the need to initialize the property with null, create the callback and bind the callback to this. The other difference is that the reference is accessed from the current property where the ref was created.

#Refs and components This is all good when we are dealing with DOM nodes in the JSX, but what about components? There are three big gotchas when dealing with components.

  • Refs are not props, it took me a while before I understood this.
  • If the component is a class component, you will get the ref from the object React creates, not the DOM node.
  • If the component is a function component, your app will crash with a reference error.

The most important part of the above limitations are that you cannot get access to the ref from this.props in Class components nor can you get it from props in functional components. So as a component author, if you need to give access to refs how do you do it?

Prior to version 16.3.0 the only way to do this was to pass it onto a prop(not ref, ref is not a prop) and then map that to the appropriate ref in the component like this:

const Section = props => (
   <section ref={ props.innerRef }>
      { props.children }
   </section>
);

This pattern works fine, but it means that there is no standard way to refer to forward the ref. It’s just a prop and can be named anything. Each component author, therefore, can use whichever convention they choose to refer to the ref.

In React 16.3.0, React.forwardRef() was introduced. This was a wrapper that gives you access to the ref as the second parameter. This means we can rewrite the above example like this:

const Section = React.forwardRef((props, ref) => (
   <section ref={ ref }>
      { props.children }
   </section>
));

Knowing how to use refs is important and I would highly recommend that you read about Refs and forwarding refs in more detail in React’s documentation.


Published: 2019-12-05
Author: Henrik Grönvall
Henrik Grönvall
Copyright © 2022 Henrik Grönvall Consulting AB