If you have been developing web applications long enough, then it is likely that at some point you have used JavaScript DOM libraries such as jQuery, Mootools, Prototype.js, etc. The advent of these libraries brought about a major shift on how interactive web applications were built. With DOM abstraction APIs, manipulating the contents of a web app became so much easier.

For example, you would find yourself doing something like this with jQuery:

$('#button').on('click', function(evt) { evt.preventDefault(); var content = $('<h1>Random Post Title</h1><p>Random post text.</p>'); $('#element').append(content); });

Following the trends for the last couple of years, you also would have noticed that the focus is now on JavaScript frameworks like React, Angular, Ember.js, VueJS, etc — for building modern day applications.

One similarity between these frameworks is that they are built with a component-based architecture. So these days, it is more common to hear about components than it is to hear about DOM-based interactions since most of those are encapsulated within the component.

While you can do a lot with these modern JavaScript frameworks by leveraging on their built-in functionalities, there are times you need to interact with the actual DOM for some native behavior. Most of the modern frameworks provide APIs through which you can access the native DOM representation of your app, and React is not an exception.

In this tutorial, we will consider how we can interact with the DOM in a React application. We will also see how we can use the React.createRef() feature introduced in React 16.3.

Refs and the DOM

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

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.

Creating refs

React provides three major ways of creating refs. Here is a list of the different methods starting from the oldest of them:

String Refs (legacy method) Callback Refs React.createRef (from React 16.3)

String refs

The legacy way of creating refs in a React application is using string refs. This is the oldest method and is considered legacy or deprecated because it will be removed in future releases of the React framework.

String refs are simply created by adding a ref prop to the desired element, passing a string name for the ref as its value. Here is a simple example:

class MyComponent extends React.Component { constructor(props) { super(props); this.toggleInputCase = this.toggleInputCase.bind(this); this.state = { uppercase: false }; } toggleInputCase() { const isUpper = this.state.uppercase; // Accessing the ref using this.refs.inputField const value = this.refs.inputField.value; this.refs.inputField.value = isUpper ? value.toLowerCase() : value.toUpperCase(); this.setState({ uppercase: !isUpper }); } render() { return ( <div> {/* Creating a string ref named: inputField */} <input type="text" ref="inputField" /> <button type="button" onClick={this.toggleInputCase}> Toggle Case </button> </div> ); } }

Here we have created a very simple React component that renders an <input> element and a <button> element that allows us to toggle the case of the input between uppercase and lowercase.

We initialized the state of the component with an uppercase property set to false . This property allows us determine the current case of the input.

The main emphasis is on the string ref we created on the <input> element. Here we created a ref to the <input> element named inputField .

Later in the click event handler for the <button> we accessed the ref via this.refs.inputField . And manipulated the DOM of the <input> element to change the value of the input.

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

Although this is a very trivial example on how to use refs, we have been able to see how string refs can be used in a React component. As stated earlier, using string refs in your React application should be discouraged.

Here is what the React documentation says concerning string refs:

If you’re currently using this.refs.textInput to access refs, we recommend using either the callback pattern or the createRef API instead.

Callback refs

Callback refs use a callback function for creating refs instead of passing the name of the ref as a string. If you are using versions of React earlier than version 16.3, then this should be your preferred method of creating refs.

The callback function receives the React component instance or HTML DOM element as its argument, which can be stored and accessed elsewhere. Using a callback ref, our previous code snippet will become the following.

class MyComponent extends React.Component { constructor(props) { super(props); this.toggleInputCase = this.toggleInputCase.bind(this); this.state = { uppercase: false }; } toggleInputCase() { const isUpper = this.state.uppercase; // Accessing the ref using this.inputField const value = this.inputField.value; this.inputField.value = isUpper ? value.toLowerCase() : value.toUpperCase(); this.setState({ uppercase: !isUpper }); } render() { return ( <div> {/* Creating a callback ref and storing it in this.inputField */} <input type="text" ref={elem => this.inputField = elem} /> <button type="button" onClick={this.toggleInputCase}> Toggle Case </button> </div> ); } }

Here we have made two major changes. First we defined the ref using a callback function and storing it in this.inputField as follows:

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

Then in the event handler, we access the ref using this.inputField instead of this.refs.inputField .

When using inline callback refs like we did in our example, it is important to know that for every update to the component, the callback function is called twice. First with null , then again with the DOM element.

However, creating the callback function as a bound method of the component class can be used to avoid this behavior.

Using a callback function for creating refs can give you more control over how the ref is created, set and unset.

Using React.createRef

Starting from React 16.3, the React API included a new createRef() method that can be used for creating refs in 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. In the following sections, we will go ahead and take a closer look at more interesting characteristics of React.createRef .

Component refs

In the previous section, we saw how we can create refs using the new React.createRef API. The actual reference is stored in the current attribute 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 class components since they create an instance of the class when mounted. Refs cannot be used on functional components.

Let’s consider a very simple 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.

component that simply wraps an 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 */} <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‘s value is not empty. Hence it returns false if the input is empty, otherwise it returns true .

which returns a boolean indicating that the input element‘s value is not empty. Hence it returns if the input is empty, otherwise it returns . 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 this snippet, I decide to use a functional component instead of a class component, since this is a stateless component. I also want to use this 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 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 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 since 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:

Track state and user interaction with components It’s important to validate that everything works in your production React app as expected. If you’re interested in monitoring and tracking issues related to components AND seeing how users interact with specific components, try LogRocket. https://logrocket.com/signup/ LogRocket is like a DVR for web apps, recording literally everything that happens on your site. The LogRocket React plugin allows you to search for user sessions where the user clicks a specific component in your app. With LogRocket you can understand how users interact with components, and surface any errors related to components not rendering. In addition, LogRocket logs all actions and state from your Redux stores. LogRocket instruments your app to record requests/responses with headers + bodies. It also records the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps. Modernize how you debug your React apps – Start monitoring for free.

Refs in uncontrolled components

By default, all components in React are controlled because React is responsible for handling updates to the component when form data changes.

When dealing with uncontrolled components in React, refs come in very handy. This is because instead of using event handlers to update state when form data changes, you rely on refs to get form values from the DOM. You can learn more about uncontrolled components here.

Let’s create a simple controlled component and then an uncontrolled component to demonstrate how we can use refs to get form values from the DOM.

class ControlledFormInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { value: "Glad" }; } handleChange(evt) { this.setState({ value: evt.target.value }); } render() { return ( <div> <h1>Hello {this.state.value}!</h1> <input type="text" value={this.state.value} onChange={this.handleChange} placeholder="Enter your name" /> </div> ) } }

The above code snippet shows a controlled component that contains an <input> element. Notice that the value of the <input> element is gotten from the value property of the state. We initialized the value on the state to “Glad” (that’s my name anyway).

Also notice that we use the handleChange() event handler to update the value property in the state with the value of the input element we got from accessing evt.target.value .

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

Here is the uncontrolled version of the component:

class UncontrolledFormInput extends React.Component { constructor(props) { super(props); this.inputField = React.createRef(); this.handleChange = this.handleChange.bind(this); this.state = { value: "Glad" }; } handleChange(evt) { this.setState({ value: this.inputField.current.value }); } render() { return ( <div> <h1>Hello {this.state.value}!</h1> {/* Attach the created ref: this.inputField */} <input type="text" ref={this.inputField} defaultValue={this.state.value} onChange={this.handleChange} placeholder="Enter your name" /> </div> ) } }

We have made a couple of changes here for the uncontrolled version of our previous component. First we create a ref using React.createRef() and store it in the this.inputField instance property. We also attach the ref to the <input> element in the render() method.

We also use the defaultValue prop to assign this.state.value as the default value of the <input> element — which is only useful up till when the input value is first changed.

If we had used the value prop to specify a default value for the input, then we will no longer get updated values from the input. This is because in React, the value prop automatically overrides the value in the DOM.

Finally, in our event handler we access the value of the input element using the ref instead of the event object. So we do this:

this.setState({ value: this.inputField.current.value });

instead of doing this:

this.setState({ value: evt.target.value });

The demo is the same as with the controlled version. Here is a sample demo of what the interaction looks like:

Conclusion

In this tutorial, we have considered various methods in which we can interact with the DOM in a React application. We have seen how we can use the new React.createRef() method introduced in React 16.3 to simplify creating refs.

We have also considered how we can use refs in uncontrolled components. You can refer to the React documentation to learn more about what you can do with refs.