React Ref

An In-depth Explanation of React Ref With Examples

June 30, 2022
Babatunde Samson
React.js

The Fastest Way to Build React UI

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

React logo
React-createref-DOM-featured-image

Introduction to React Ref

Like many other UI libraries, React offers a way to rethink a view resulting from a component’s state with React Ref. This is a significant pivot away from how we usually build applications.

When we become familiar with some of these new concepts, we discover how easy it is to solve simple problems that used to cause us trouble in the frontend world. Part of that benefit comes from creating views with the abstraction mechanisms that React and JSX provide instead of doing it through DOM spec methods.

The React team introduced something innovative: they provided an escape hatchstar-struck . They kept the library open for situations beyond those they are explicitly designed for, and conditions in the model may not work. We’ll cover anti-patterns later in this article. 

Typically, React state updates the data on the screen by re-rendering the react component for us. But there are certain situations where you need to deal with the DOM properties directly, and that’s where React refs come in clutch. These escape hatches are React refs, which allow us to access DOM properties directly. So, let’s get up to date!

What is React ref?

React provides a feature known as refs that allow for DOM access from components. You simply attach a React ref to an element in your application to provide access to the element’s DOM from anywhere within your react component.

React Refs can also be used to provide direct access to React elements and not just DOM nodes. Here is what the React documentation says concerning refs:

  • Refs provide a way to access DOM nodes or React elements created in the render method.

Generally, the use of refs should be considered only when the required interaction cannot be achieved using the mechanisms of state and props.

However, there are a couple of cases where using a ref is appropriate. One of which is when integrating with third-party DOM libraries. Also, deep interactions such as handling text selections or managing media playback behavior also require the use of refs on the corresponding elements. You can check out our React reference guide to learn more.

When to Use React Ref

There are a few good use cases for React ref:

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

Creating refs in React

When working with class-based components in the past, we used createRef() to create a ref. However, now that React recommends functional components, and general practice is to follow the react Hooks way of doing things, we don’t need to use createRef(). Instead, we use useRef(null) to create refs in a functional component, which may be against your first inclination.

As stated in the intro, refs are escape hatches for React developers, and we should try to avoid using them if possible.

When we obtain a node using a ref and later modify some attribute or the DOM structure of it, it can enter into conflict with React’s diff and update approaches.

First, let’s start with a simple react component and grab a node element using refs.

import React from "react";
const ActionButton = ({ label, action }) => {
  return <button onClick={action}>{label}</button>;
};
export default ActionButton;

The <button> expression here is actually the JSX way of calling the React.createElement(‘button’) statement, which is not actually a representation of an HTML button element — it’s a React element.

You can gain access to the actual HTML element by creating a React reference and passing it to the element itself.

import React, { useRef } from "react";
const ActionButton = ({ label, action }) => {
  const buttonRef = useRef(null);
  return (
    <button onClick={action} ref={buttonRef}>
      {label}
    </button>
  );
};
export default ActionButton;

This way, at any time in the lifecycle of the react component, we can access the actual HTML element at buttonRef.current.

Now we know how to access DOM nodes inside a React component. Let’s take a look at some of the situations where this may be useful.

Using refs in React

One of the many concepts that React popularized among developers is the concept of declarative views. Before declarative views, most of us were modifying the DOM by calling functions that explicitly changed it.

As mentioned at the introduction of this article, we are now declaring views based on a state, and — though we are still calling functions to alter this state — we are not in control of when the DOM will change or even if it should change.

Because we are not in more control, we’d lose this imperative nature if it weren’t for refs.

Here are a few use cases where it may make sense to bring refs into your code.

Focus control

You can achieve focus in an element programmatically by calling focus() on the node instance.

Because the DOM exposes this as a function call, the best way to do this in React is to create a ref and manually do it when we think it’s suitable.

import React, { useState } from "react";
const InputModal = ({ initialValue, onSubmit, onClose }) => {
  const [value, setValue] = useState(initialValue);
  const onChange = (e) => {
    setValue(e.target.value);
  };
  const onSubmit = (e) => {
    e.preventDefault();
    onSubmit(value);
    onClose();
  };
  return (
    <div className="modal--overlay">
      <div className="modal">
        <h1>Insert a new value</h1>
        <form action="?" onSubmit={onSubmit}>
          <input type="text" onChange={onChange} value={value} />
          <button>Save new value</button>
        </form>
      </div>
    </div>
  );
};
export default InputModal;

In the modal above, we allow the user to modify a value already set in the screen below. It would be a better user experience if the input was on focus when the modal opens, which could enable a smooth keyboard transition between the two screens.

The first thing we need to do is get a reference for the input:

import React, { useRef, useState } from "react";
const InputModal = ({ initialValue, onSubmit, onClose }) => {
  const [value, setValue] = useState(initialValue);
  const inputRef = useRef(null);
  const onChange = (e) => {
    setValue(e.target.value);
  };
  const onSubmit = (e) => {
    e.preventDefault();
    onSubmit(value);
    onClose();
  };
  return (
    <div className="modal--overlay">
      <div className="modal">
        <h1>Insert a new value</h1>
        <form action="?" onSubmit={onSubmit}>
          <input ref={inputRef} type="text" onChange={onChange} value={value} />
          <button>Save new value</button>
        </form>
      </div>
    </div>
  );
};
export default InputModal;

Next, when our modal mounts, we imperatively call focus on our input ref within a useEffect:

import React, { useEffect, useRef, useState } from "react";

const InputModal = ({ initialValue, onSubmit, onClose }) => {
  const [value, setValue] = useState(initialValue);
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, [])
  const onChange = (e) => {
    setValue(e.target.value);
  };
  const onSubmit = (e) => {
    e.preventDefault();
    onSubmit(value);
    onClose();
  };
  return (
    <div className="modal--overlay">
      <div className="modal">
        <h1>Insert a new value</h1>
        <form action="?" onSubmit={onSubmit}>
          <input ref={inputRef} type="text" onChange={onChange} value={value} />
          <button>Save new value</button>
        </form>
      </div>
    </div>
  );
};
export default InputModal;

So when you open the modal, you should see the text box focused by default:

Text box via React Ref

Remember that you need to access the element through the current property.

Using the React.createRef

Starting from React 16.3, the React API included a createRef() method that can be used for creating refs in React much the same way as we did using the callback function. You simply create a ref by calling React.createRef() and assign the resulting ref to an element.

Using React.createRef(), here is what our previous example will look like:

class MyComponent extends React.Component {

  constructor(props) {
    super(props);
    this.inputField = React.createRef();
    this.toggleInputCase = this.toggleInputCase.bind(this);
    this.state = { uppercase: false };
  }
  
  toggleInputCase() {
    const isUpper = this.state.uppercase;
    
    // Accessing the ref using this.inputField.current
    const value = this.inputField.current.value;
    
    this.inputField.current.value =
      isUpper
        ? value.toLowerCase()
        : value.toUpperCase();
        
    this.setState({ uppercase: !isUpper });
  }

  render() {
    return (
      <div>
        {/* Referencing the ref from this.inputField */}
        <input type="text" ref={this.inputField} />
        
        <button type="button" onClick={this.toggleInputCase}>
          Toggle Case
        </button>
      </div>
    );
  }
  
}

Here we see a couple of changes. First, in the constructor(), we created a ref using React.createRef() and stored it in this.inputField as follows:

this.inputField = React.createRef();

Next, in the event handler, we access the ref using this.inputField.current instead of this.inputField. This is worth noting for refs created with React.createRef(). The reference to the node becomes accessible at the current attribute of the ref.

Finally, we pass the ref to the <input> component as follows:

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

We have explored the various methods of creating refs in our React application. 

Using the React useRef Hook

With its release in React v16, the Hooks API has become the de facto means of abstracting and reusing code in React applications. One such Hook is React useRef, which allows us to create and use refs in a functional component.

Note that even with the Hook you still, by default, cannot use the ref attribute on functional components because we cannot create instances of functions. We will discuss how to get around this with ref forwarding later on in this article.

To use useRef, you pass in the object that ref.current should refer to the Hook and call it. This Hook call should return a ref object that you can now use as if you were using the createRef function discussed earlier.

Here’s what our previous example should look like if we use the Hook to return a ref object:

const MyComponent = () => {
    const [uppercase, setUppercase] = React.useState(false)
    const inputField = React.useRef(null)
    const toggleInputCase = () => {
        // Accessing the ref using inputField.current
        const value = inputField.current.value;
        inputField.current.value = uppercase ? value.toLowerCase() : value.toUpperCase();
        setUppercase(previousValue => !previousValue)
    }

    return(
       <div>
           {/* Referencing the ref from this.inputField */}
           <input type="text" ref={inputField} />
           <button type="button" onClick={toggleInputCase}>
                Toggle Case  
           </button>
       </div>

As you can see, the code is pretty similar to that of the React.createRef implementation. We create a ref using the useRef Hook and pass that ref to the ref attribute of the <input> HTML element.

For the <button> element’s event handler, the process is also the same as before. We update the value property of the HTML element that our ref points (which can be accessed by using ref.current) depending on the current value of the state variable, uppercase.

Creating React component refs

In the previous section, we saw how we can create refs using the React.createRef API. The actual reference is stored in the attribute current of the ref.

In our examples so far, we have only created refs to DOM nodes in our application. But it is also possible to create refs to React components, which will give us access to the instance methods of such components.

Note that we can only create refs on a class component since they create an instance of the class when mounted. Refs cannot be used on functional components.

Let’s consider the following example to demonstrate using refs on React components. We will create two components in this example:

  • FormInput component that simply wraps an <input> element and provides two methods. One for knowing when the input contains some value, and the other for selecting the input text.
  • MyComponent simply wraps the FormInput component and a button to select the text in the input when clicked.

Here are the code snippets for the components:

class FormInput extends React.Component {

  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  
  hasText() {
    return this.textInput.current.value.length > 0;
  }
  
  selectInputText() {
    this.textInput.current.select();
  }
  
  render() {
    return (
      <div>
        {/* Creating a string ref named: textInput */}
        &lt;input type="text" ref={this.textInput} />
      </div>
    );
  }
  
}

Like before, we created a ref using React.createRef() and added the ref to the <input> element in the render function. We created two methods:

  • hasText(), which returns a Boolean indicating that the input element value is not empty. Hence, it returns false if the input element is empty, otherwise it returns true.
  • selectInputText(), which makes a selection of the whole text in the input element. Notice that we get a reference to the input element in our methods by accessing the current attribute of the ref we created, that is: this.textInput.current.

Now let’s go ahead and create MyComponent. Here is the code snippet:

const MyComponent = (props) => {

  const formInput = React.createRef();
  
  const inputSelection = () => {
    const input = formInput.current;
    
    if (input.hasText()) {
      input.selectInputText();
    }
  };
  
  return (
    <div>
      <button type="button" onClick={inputSelection}>
        Select Input
      </button>
      
      <FormInput ref={formInput} />
    </div>
  );
  
};

In the code snippet above, we use a functional component instead of a class component, as this is a stateless component. We also want to use this react component to demonstrate how refs can be used inside functional components.

Here we create a ref using React.createRef() and store it in the formInput constant. Notice here that we are not using this, since functional components are not classes and hence do not create instances.

Notice in the render() method that we added the ref we created to the <FormInput> component we created earlier. Unlike the previous examples where we were adding refs to DOM nodes, here, we are adding a ref to a react component.

Note that a ref can only be created for a class component and not a functional component. FormInput is a class component so we can create a reference to it. However, we can use a ref inside a functional component like we did in this example using formInput.

Finally, in the inputSelection() function, we access the reference to our React component using the current attribute of the ref as before.

Notice that we are able to get access to the hasText() and selectInputText() methods of the FormInput component because the reference points to an instance of the FormInput component. This validates why refs cannot be created for functional components.

Here is a sample demo of what the interaction looks like:

Sample demo of React Ref

How to avoid React ref anti-patterns

Once you know how refs work, it’s easy to use them where they’re not needed. There’s more than one way to achieve the same thing inside a React component, so it’s easy to fall into an anti-pattern.

My rule when it comes to ref usage is this: only use a ref when you need to imperatively call a function for a behavior React doesn’t allow you to control.

A simpler way to put it would be: when you need to call a function, and that function has no association with a React method or artifact, use a ref.

Let’s explore an anti-pattern that‘s common.

import React, { useRef } from "react";
const Form = () => {
  const [storedValue, setStoredValue] = useState("");
  const inputRef = useRef(null);
  const onSubmit = (e) => {
    e.preventDefault();
    setStoredValue(inputRef.current.value);
  };
  return (
    <div className="modal">
      <form action="?" onSubmit={onSubmit}>
        <input ref={inputRef} type="text" />
        <button>Submit</button>
      </form>
    </div>
  );
};

It’s fair to say if you want to send a value on submit, this approach will work, but the issue here is that because we know refs provide an escape hatch of the view model React offers, we can too easily go sniffing into DOM elements or properties that we can access through React’s interface.

Controlling the input value means we can always check its value. We don’t need to use refs here to access the value of the text box. We can use the value provided by React itself:

return (
  <input
    type="text"
    onChange={e => setValue(e.target.value)}
    value={value}
  />
)

Let’s go back to our rule: “Only use a ref when you need to imperatively call a function for a behavior React doesn’t allow you to control. ”

React forwardRefs 

As we’ve discussed, refs are actually useful for really specific actions. The examples shown are a little simpler than what we usually find in a web application codebase nowadays. Components are more complex, and we barely use plain HTML elements directly. It’s really common to include more than one node to encapsulate more logic around the view behavior.

import React from 'react'

const LabelledInput = (props) => {
  const { id, label, value, onChange } = props

  return (
    <div class="labelled--input">
      <label for={id}>{label}</label>
      <input id={id} onChange={onChange} value={value} />
    </div>
  )
}

export default LabelledInput

Conclusion

In this tutorial, we have considered various methods and use cases in which we can interact with the DOM in a React application. We’ve also seen how we can use the new React.createRef() method and useRef introduced in React to simplify creating refs. You can keep up to date by regularly revisiting this article.

Resources

Related Articles

  • React.js

    Understanding How to Implement a React Portal

    In a React application, by convention, you need to follow a parent-child component hierarchy. While building a React app, you might encounter scenarios where you want to render children into a DOM node outside of its component hierarchy like a…

    August 23, 2022
  • React Best Practices

    How to Convert Figma to React

    Introduction The front-end development ecosystem is continuously evolving and new trends come and go daily. From UX prototyping tools to Web development frameworks, there is a lot of innovation happening. New tools are dropped more frequently than ever, especially in…

    June 9, 2022

Convert Figma To React

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Convert Design to Code