Ohans Emmanuel Author, Understanding Redux. I Love God. I Love GF a little too much 💕🤣 http://thereduxjsbooks.com

React Reference Guide: Refs and the DOM

7 min read 2029

React Reference Guide: Refs and the DOM


The standard way for a parent component to interact with its child elements is via props, e.g., to modify a child, you’d re-render it with new props. That’s not news.

However, what happens when you need to access DOM nodes or React elements created in the render method of your components? Props don’t exactly help you out here.

Well, this is where refs come in handy.

Jump ahead:


When to use refs

The best use cases for refs arise when you’re trying to perform imperative actions such as:

  • Managing focus, text selection, or media playback
  • Working with imperative animation
  • Integrating with third-party DOM libraries

To build more consistent React apps, you should avoid using refs for anything that can be done declaratively, i.e., via the standard React data flow with state and props. For example, instead of exposing open() and close() methods on a Modal component, pass an isOpen prop and have that managed internally via React state.


Don’t overuse refs

When you first encounter refs or are new to React, you may fall into the trap of thinking refs are an easy way to “get things done” in your app. They certainly fit the imperative model you may be used to working with.

If you find yourself thinking this, take a moment to re-evaluate how such data flow could be resolved via the typical React data flow i.e via state and props.

Oftentimes, the problem at hand may be solved by just lifting state to a parent component higher in the component tree and passing that state value down to the needed component/element.

Check out our tutorial on how to lift a component’s state using React Suspense.

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

N.B., the next sections show examples of how to work with Refs. These examples use React.createRef, the API introduced in React 16.3. If you’re using an earlier version of React, see the section below on using callback refs.


Creating refs

There are two steps to creating ref objects.

1.) Create a ref object using React.createRef:

class App extends React.Component {
  // see this 
  this.myRef = React.createRef()
}

You create a ref by invoking the createRef object and typically assign it to an instance property, e.g., this.myRef as seen in the example above. This is done so that the ref can be referenced throughout the component.

2.) Reference the created ref in the render method:

class App extends React.Component {
   myRef = React.createRef()
   render() {
    //look here
    return <div ref={this.myRef}>I am a div</div>
  }
}

After creating the ref object, you pass it on to the required element via the special ref attribute.


Accessing refs

After passing a ref to an element in the component render method, a reference to the DOM node becomes accessible, as seen below:

const domNode = this.myRef.current

Note that the current property is referenced on the created ref object, where this.myRef represents the ref passed to a DOM node via the ref attribute.

Look at the code block above. What value does the variable domNode contain? Well, that depends on the type of node the ref attribute is passed to — it’s always different.

Here are the different options:

1.) When the ref attribute is passed to an HTML element, the ref object receives the underlying DOM element as its current property.

//render 
<div ref={this.myRef} />

//node contains HTMLElement (div) 
const node = this.myRef.current

2.) When the ref attribute is passed to a custom class component, the ref object receives the mounted instance of the component.

//render 
<MyClassComponent ref={this.myRef} />

//node contains MyClassComponent class instance
const node = this.myRef.current

3.) Refs can’t be passed to a function component because they don’t have instances.

//render: don't do this. 
<MyFunctionalComponent ref={this.myRef}

The next sections will demonstrate examples of the aforementioned node types.

Adding a ref to a DOM element

Consider an example of focusing a text input on clicking a button:

Focusing a Text Input on Clicking a Button

To do this, create a ref object that holds the text input DOM node and access the DOM API to focus the input, as show below:

class App extends React.Component {
  // create a ref to hold the textInput DOM element
  textInput = React.createRef();

  focusTextInput = () => {
    // get the input dom node from the ref object
    const inputDOMNode = this.textInput.current;
    // Use the browser imperative api: call the focus method
    inputDOMNode.focus();
  };

  render() {
    return (
      <div>
        {/* pass a ref attribute to the input element */}
        <input type="text" ref={this.textInput} />
        <input
          type="button"
          value="Focus input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

Note that when the component mounts, React assigns the current property of the ref object with the input DOM element. When the component is unmounted, this is assigned to null.

The ref updates are also guaranteed to happen before the componentDidMount or componentDidUpdate lifecycle methods. See a demo below.

Adding a ref to a class component

What if we decided to refactor the previous example to pass a ref attribute to a class component, e.g., MyTextInput? How much changes?

Take a look below:

class CustomTextInput extends React.Component {
  myInputRef = React.createRef();
  focusTextInput = () => {
    // get dom node from ref props
    const inputDOMNode = this.myInputRef.current;
    inputDOMNode.focus();
  };

  render() {
    // pass ref on from props
    return <input type="text" ref={this.myInputRef} />;
  }
}

We have a class component, CustomTextInput, with a focusTextInput function. Note that the CustomTextInput component doesn’t invoke this function but creates and accesses the text input DOM node.

So, if you wanted to trigger this function handler from another component, e.g., to focus the input on mount or when a button is clicked, the following works perfectly:

class App extends React.Component {
  // create a ref to hold the textInput DOM element
  textInputClassInstance = React.createRef();

  focus = () => {
    // call the child handler
    // this is possible because the ref object received the child class instance
    this.textInputClassInstance.current.focusTextInput();
  };

  componentDidMount() {
    this.focus();
  }

  render() {
    return (
      <div>
        {/* pass a ref attribute to the class component. Ref receives the class instance  */}
        <CustomTextInput ref={this.textInputClassInstance} />
        <input type="button" value="Focus input" onClick={this.focus} />
      </div>
    );
  }
}

See a demo below:

Refs and function components

Remember that you should not use the ref attribute on function components because they don’t have instances:

class Parent extends React.Component {
  textInput = React.createRef();

  render() {
    // This will NOT work!
    return (
      <MyFunctionComponent ref={this.textInput} />
    );
  }
}

If you want to pass a ref to a function component, consider using forwardRef, perhaps in conjunction with useImperativeHandle. You could also consider converting said component to a class component.

See useImperativeHandle in action in our React Hooks reference guide.

As stated earlier, you shouldn’t pass a ref to a functional component, but you can create and use refs within functional components, as seen below:

function CustomTextInput(props) {
  // create ref object using useRef
  const textInput = useRef(null);

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

  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}

Exposing DOM refs to parent components

Occasionally, you may want to access a child’s DOM node from a parent component. This is far from ideal as it breaks component encapsulation. However, there are legitimate use cases that could warrant this technique, e.g., triggering focus or measuring the size or position of a child DOM node.

So, how should you approach this problem?

Firstly, you could add a ref to the child component, as we did in an earlier example. This is not a perfect solution since you get a component instance and not the child DOM node. Also, this doesn’t work with functional components.

With React 16.3 or higher, consider using ref forwarding for such cases. Ref forwarding allows you to expose a child’s DOM node to a parent component.

Ready to go deeper? Read our advanced tutorial on forwardRef.

With React 16.2 or lower, ref forwarding isn’t supported. So what do you do? Consider explicitly passing a ref as a differently named prop.

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

class Parent extends React.Component {
  // create ref object 
  inputElement = React.createRef();

  render() {
    return (
       {/* pass ref object as a differently named prop: inputRef*/}
      <CustomTextInput inputRef={this.inputElement} />
    );
  }
}

Note that the approach above requires some code to be added to the child component. In more stringent use cases, you may have no control over the child component implementation. Your best bet is to use findDOMNode(), but note that this is discouraged and deprecated in StrictMode.

It is highly recommended to avoid exposing DOM nodes if you don’t need do.


Callback refs

In all the examples we’ve discussed, the ref object has been directly passed on via the ref attribute. However, React supports another way to set refs; these are called callback refs.

Instead of passing a ref object via the ref attribute:

myRefObject = React.createRef()
...
<div ref={myRefObject} />

Pass a function:

myRefCallback = (node) => {}
...
<div ref={myRefCallback} />

React will pass the component instance or DOM element as an argument to the function. This can then be stored and accessed elsewhere!

Callback refs give you a lot more control over actions to be performed when refs are set and unset. Remember that the ref callback is called with the DOM element when the component mounts, and it is called with null when it unmounts.

Consider the example below, which creates a ref callback and stores a DOM node in an instance property — a commonly implemented pattern.

class App extends React.Component {
  //create instance variable
  textInput = null;

  setTextInputRef = (element) => {
    // save DOM element received in the instance variable: textInput
    this.textInput = element;
  };

  focusTextInput = () => {
    // Focus the text input using the raw DOM API
    if (this.textInput) this.textInput.focus();
  };

  componentDidMount() {
    // autofocus the input on mount
    this.focusTextInput();
  }

  render() {
    // Use the `ref` callback to store a reference to the text input DOM
    return (
      <div>
        <input type="text" ref={this.setTextInputRef} />
        <input
          type="button"
          value="Focus  input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

See a demo below:

As with object refs, you can pass callback refs between components, e.g., parent and child. See the example below:

function CustomTextInput(props) {
  return (
    <div>
      {/*child component passes ref to callback*/}
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      {/* pass a callback ref to the child component*/}
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

In the example above, the Parent component holds the DOM value corresponding to the child text input in the instance variable inputElement.

Note that the ref callback is passed to the child component, CustomTextInput via a differently named prop: inputRef. CustomTextInput then passes the callback on to the ref attribute set on the <input> element.

Caveats with callback refs

If you define a ref callback as an inline function, a new instance of the function will be created with each render. The callback is then called twice: first will null, and then again with the DOM element. Essentially, React has to clear the old ref and set up a new one.

This shouldn’t matter so much in most cases, but to avoid this, define the ref method as a bound method on the class (for class components).


Legacy API: String refs

It is worth mentioning that an older ref API supported ref attributes as plain strings, e.g., myTextInput and the DOM node accessed as this.refs.myTextInput.

Don’t do this anymore. String refs are now considered legacy, they have some issues, and they are likely going to be removed in a future release of React.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    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 — .

    Ohans Emmanuel Author, Understanding Redux. I Love God. I Love GF a little too much 💕🤣 http://thereduxjsbooks.com

    Leave a Reply