Refs and the DOM

Prerequisites

Refs and the DOM – React official docs https://reactjs.org/docs/refs-and-the-dom.html

In the typical React dataflow, 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 dataflow. 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.

When to Use Refs

There are a few good use cases for refs:

  • Managing focus, text selection, or media playback.

  • Triggering imperative animations.

  • Integrating with third-party DOM libraries.

    Avoid using refs for anything that can be done declaratively.

    For example, instead of exposing open() and close() methods on a Dialog component, pass an isOpen prop to it.

Don’t Overuse Refs

Your first inclination may be to use refs to “make things happen” in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to “own” that state is at a higher level in the hierarchy. See the Lifting State Up chapter for examples of this.

Adding a Ref to a DOM Element

React supports a special attribute that you can attach to any component. The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted. When the ref attribute is used on an HTML element, the ref callback receives the underlying DOM element as its argument. For example, this code uses the ref callback to store a reference to a DOM node:

Here is an example we have on our Tab Item component

class Item extends React.Component {
  constructor(props) {
    super(props);
    this.setButtonRef = this.setButtonRef.bind(this);
  }

  componentDidMount() {
    const {focusedNode} = this;
    const {focused} = this.props;

    if (focusedNode && focused) {
      focusedNode.focus();
    }
  }

  setButtonRef(button) {
    this.focusedNode = button;
  }

  render() {
    return (
      <li>
        <button ref={this.setButtonRef}>
          {children}
        </button>
      </li>
    );
  }
}

React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts. ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks. Using the ref callback just to set a property on the class is a common pattern for accessing DOM elements. The preferred way is to set the property in the ref callback like in the above example.

Adding a Ref to a Class Component

When the ref attribute is used on a custom component declared as a class, the ref callback receives the mounted instance of the component as its argument. For example, if we wanted to wrap our Item above to simulate it being clicked immediately after mounting:

class AutoFocusItem extends React.Component {
  constructor(props) {
    super(props);
    this.setItemRef = this.setItemRef.bind(this);
  }

  componentDidMount() {
    this.item.focusTextInput();
  }

  setItemRef(itemComponent) {
    this.item = itemComponent;
  }

  render() {
    return (
      <Item ref={this.setItemRef} />
    );
  }
}

Note that this only works if Item is declared as a class.

Functional components

You may not use the ref attribute on functional components because they don’t have instances However, you can use the ref attribute inside a functional component as long as you refer to a DOM element or a class component:

function CustomTextInput(props) {
  // textInput must be declared here so the ref callback can refer to it
  let textInput = null;

  function handleClick() {
    textInput.focus();
  }

  return (
    <div>
      <input ref={(input) => { textInput = input; }} />
      <input onClick={handleClick} />
    </div>
  );  
}

Exposing DOM Refs to Parent Components

In rare cases, you might want to have access to a child’s DOM node from a parent component. This is generally not recommended because it breaks component encapsulation, but it can occasionally be useful for triggering focus or measuring the size or position of a child DOM node.

While you could add a ref to the child component, this is not an ideal solution, as you would only get a component instance rather than a DOM node. Additionally, this wouldn’t work with functional components.

Instead, in such cases we recommend passing down a callback function as prop (with an arbitrary name, e.g. inputRef) to your children component and attach it to the DOM node as a ref attribute. This lets the parent pass its ref callback to the child’s DOM node through the component in the middle. Another benefit of this pattern is that it works several components deep. See the following example:

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

function Parent(props) {
  return (
    <div>
      My input: <CustomTextInput inputRef={props.inputRef} />
    </div>
  );
}


class Grandparent extends React.Component {
  render() {
    return (
      <Parent
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

Here, the ref callback is first specified by Grandparent, then passed to the Parent as a regular prop called inputRef and finally passed to CustomTextInput as a prop too. As a result, this.inputElement in Grandparent will be set to the DOM node corresponding to the <input> element in the CustomTextInput.

Final note

All things considered, we advise against exposing DOM nodes whenever possible, but this can be a useful escape hatch. Note that this approach requires you to add some code to the child component. If you have absolutely no control over the child component implementation, your last option is to use findDOMNode(), but it is discouraged.

Last updated