Fullstack React's Guide to using Refs in React Components

When using React, our default mindset should be that we don't imperatively modify the DOM, but instead that we pass in props and then re-render the component. But sometimes there are situations where imperative actions are necessary.

Refs in React provides a way to access the React elements (or DOM nodes) created in the render() method.

When parent components need to interact with children components, we typically use props However, in some cases we might need to modify a child without re-rendering it with new props. That's when refs get the spotlight.

When Should I use Refs?

We advise to use refs in the following situations:

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

So once we've determined that we really should be using refs, how do we use them?

Using Refs in React

There are various ways in which you can use refs:

  • React.createRef()
  • Callback refs
  • String refs (legacy)
  • Forwarding refs

Let's look at each one of these in turn.

React.createRef()

Refs can be created by using the React.createRef() function and attached to an HTML element in a React component via the ref attribute.

A ref is usually created inside a component's constructor so as to make it usable throughout the component. For example:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.firstRef = React.createRef();
  }
  render() {
    return <div ref={this.firstRef} />;
  }
}

As you can see above:

  • a ref instance is created in the constructor as this.firstRef and
  • it's assigned to a ref in the div inside the render() function. Let's look at an example of how to use refs in a React component.

Focus an Input using Refs

Here's another example:

// Ref.js
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // create a ref to store the textInput DOM element
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // Explicitly focus the text input using the raw DOM API
    // Note: we're accessing "current" to get the DOM node
    this.textInput.current.focus();
  }

  render() {
    // tell React that we want to associate the <input> ref
    // with the `textInput` that we created in the constructor
    return (
      <div>
        <input type="text" ref={this.textInput} />

        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

In the code block above, we've built a button that automatically focuses on an input field when it's clicked on.

We start by creating a ref instance and setting it to this.textInput in the constructor method and then assigning it to the input field via the ref attribute.

<input type="text" ref={this.textInput} />

Note that when the ref attribute is used on an HTML element (in this case the input field), the ref created in the constructor (with React.createRef()) receives the underlying DOM element in the current value.

This means to access the DOM value, we need to write something like this:

this.textInput.current

The second input field is the button that will be clicked on to auto focus on the first input field. It has an onClick attribute set to the this.focusTextInput function.

<input
  type="button"
  value="Focus the text input"
  onClick={this.focusTextInput}
/>

The focusTextInput() function uses the JavaScript standard DOM function .focus() to focus the cursor on the text input box.

focusTextInput() {
  this.textInput.current.focus();
}

Lastly, the focusTextInput function is bound in the constructor method like this:

this.focusTextInput = this.focusTextInput.bind(this);

Getting Values from a ref

In this example, we'll see how to set an input field to a ref and then get the value of the ref. Here's what the example will look like:

In this example, we create an input field where we enter a value. Then, when the submit button is clicked, we'll read this value and log it to the console.

// Ref.js
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // create a ref to store the textInput DOM element
    this.textInput = React.createRef();
  }
  handleSubmit = e => {
    e.preventDefault();

    console.log(this.textInput.current.value);
  };

  render() {
    // tell React that we want to associate the <input> ref
    // with the `textInput` that we created in the constructor
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <input type="text" ref={this.textInput} />
          <button>Submit</button>
        </form>
      </div>
    );
  }
}

Again, we use the React.createRef() function to create a ref instance and then we assign it to the instance variable this.textInput.

In the render function, the form contains the input field whose value we want to read. How do we read this value? By assigning a ref to the input and then reading the value from that ref.

<input type="text" ref={this.textInput} />

The form has an onSubmit function of this.handleSubmit which logs the value of the input field to the console.

handleSubmit = e => {
  e.preventDefault();
  console.log(this.textInput);
};

Above, the e parameter contains the event object. We use e.preventDefault() to tell our browser that we are dealing with the submit button being clicked and we don't want this event to "bubble up" (that is, be handled by the browser outside of this code).

In the interactive example if we log this.textInput we're given an Object -- this is the ref object:

> Object {current: HTMLInputElement}

Notice that it has one property current, which is an HTMLInputElement. This is the input DOM element itself and not the actual value. To get at the value of the input tag, we have to acess this.textInput.current.value as seen below:

handleSubmit = e => {
  e.preventDefault();
  console.log(this.textInput.current.value);
};

Using refs is a straightforward way to get the values from form controls: just assign a ref to an input field and extract the value when you need it.

Download the (free) first chapter of our React Book

Callback Refs

The callback ref is another way of using refs in React. To use refs in this way we set the ref property to a callback function. When we set a ref, React will call this function, passing the element as the first argument.

Here's the code for another example. Like previous example above this code gets the text value of an input tag, but here we use callback refs instead:

// Refs.js
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };
  }

  handleSubmit = e => {
    e.preventDefault();
    console.log(this.textInput.value);
  };

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <input type="text" ref={this.setTextInputRef} />
          <button>Submit</button>
        </form>
      </div>
    );
  }
}

In the example above, the input tag has a ref set to this.setTextInputRef. this.setTextInputRef.

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 and componentDidUpdate lifecycle hooks.)

String Ref (Legacy API)

There is another way to set refs, but it's considered legacy and likely to be deprecated soon. But you might see it in other people's code, so it's worth mentioning here.

With string refs, you'll see the markup of the input tag set to something like:

<input type="text" ref="textInput" />

And then on the component, we'd get the value like this: this.refs.textInput.value - but, again, this should not be done in new code as the API will be deprecated.

Forwarding Refs

Ref forwarding is a technique for passing a ref through a component to one of its children. It's very useful for cases like reusable component libraries and Higher Order Components (HOC).

You can forward a ref to a component by using the React.forwardRef function. Let's look at an example below:

// Ref.js
const TextInput = React.forwardRef((props, ref) => (
  <input type="text" placeholder="Hello World" ref={ref} />
));

const inputRef = React.createRef();

class CustomTextInput extends React.Component {
  handleSubmit = e => {
    e.preventDefault();
    console.log(inputRef.current.value);
  };

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <TextInput ref={inputRef} />
          <button>Submit</button>
        </form>
      </div>
    );
  }
}

Ref forwarding allows components to take a ref they receive and pass it further down (in other words, "forward" it) to a child.

In the example above, we have a component called TextInput that has a child which is an input field. So how do we pass or forward the ref down to the input?

First, we start by creating a ref with the line of code below:

const inputRef = React.createRef()

Then, We pass our ref down to <TextInput ref={inputRef}> by specifying it as a JSX attribute. React then forwards the ref to the forwardRef function as a second argument.

Next, We forward this ref argument down to <input ref={ref}>. The value of the DOM node can now be accessed at inputRef.current.

Forwarding refs and higher-order components

Finally, let's look at another example of using refs but this time with Higher Order Components (HOC).

In the example app above, every keypress in the input field is logged to the console. The input field has a ref assigned to it and the idea is to see how refs are passed/forwarded down in Higher Order Components.

const Input = InputComponent => {
  const forwardRef = (props, ref) => {
    const onType = () => console.log(ref.current.value);
    return <InputComponent forwardedRef={ref} onChange={onType} {...props} />;
  };
  return React.forwardRef(forwardRef);
};

Here there is a Higher Order Component named Input which accepts InputComponent as an argument. It also logs the value of the ref to the console on every keypress.

In the Input HOC, the forwardRef function returns the InputComponent. A ref is then created by the React.forwardRef function which contains the forwardRef function. The Input component will return the value.

Next we create the component that will be the child of the Input Higher Order Component.

const TextInput = ({ forwardedRef, children, ...rest }) => (
   <div>
     <input ref={forwardedRef} {...rest} />
     {children}
  </div>
);

The component above has forwardedRef assigned to ref so that the input field can accept a ref when rendered in a child component. The destructured ...rest allows us to spread the props (that is, pass all arguments in the rest array down as props to the input tag). So how do we use the TextInput component? Like this:

const InputField = Input(TextInput);
class CustomTextInput extends Component {
   render() {
    const inputRef = React.createRef();
    return <InputField ref={inputRef} />;
   }
}

Finally, the Input Higher Order Component along with it's child component, TextInput is set to the InputField component.

The InputField component is then rendered with ref being passed to it.

Conclusion

Refs are a great way to pass data down to a particular child instance in a way that's different from via props and state.

You have to be careful because refs manipulate the actual DOM as opposed to virtual DOM which is contradictory to the React mindset. So while refs shouldn't be the default method for flowing data through your application, they can be a great way to read data from DOM elements, when you have to.

Download the (free) first chapter of our React Book


Yomi Eluwande

Yomi is a Javascript Developer who works at Flutterwave. He enjoys working on products that solves problems and also a technical writer who writes about the wonders of JavaScript.

You can view his blog on Medium here, or follow him on Twitter