Peter Ekene Eze Learn, Apply, Share

Why you should use refs sparingly in production

4 min read 1243

React refs make it possible for you to directly access the DOM in React. This is an important feature to have as you will occasionally need to perform certain actions on the DOM as you develop your React applications. In this post, we’ll take a closer look at React refs and try to understand why it might not be a great idea to use them in production-grade applications.

Use cases

As much as using the ref attribute gives you the ability to perform certain operations and directly manipulate the DOM. It is a general rule of thumb to avoid using refs unless you absolutely have to. The official React documentation outlined only three possible use cases where refs are entirely considered useful for lack of better alternatives:

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

In the first case, the ref attribute gives you the ability to interact with the DOM API and perform actions such as getting the value of an input element and managing user focus as well as managing media elements.

Imperative animations require access to the responsible DOM nodes to manipulate the target elements. This is what the ref attributes help you achieve, by performing actions on selected elements at different parts of your application.

Lastly, some libraries are heavily dependent on the DOM to function. Examples are maps, editors, image manipulation tools, etc. These components need complete access to the DOM and can only gain such access through the ref attributes in React.

How refs work in development

Before now we used the this.ref object to interact with the DOM, this feature has now been deprecated and replaced with the new createRef that shipped with React 16.3. With it, you can create a ref by calling React.createRef() and attaching a React element to it using the ref attribute on the element:

import React, { Component } from 'react';
class RefDemo extends Component {
  constructor(props) {
    super(props);
    this.nameInput = React.createRef(); 
  }
  render() {
    return (
      <input ref={this.nameInput}/>
    );
  }
}

With the above implementation, we can access the DOM node of the ref we just created like this:

this.nameInput.current

Subsequent actions can then be performed on this node. For instance, to get the value of the input element, we’ll do:

import React, { Component } from 'react';
class RefDemo extends Component {
  constructor(props) {
    super(props);
    this.state = { value: "" }
    this.nameInput = React.createRef(); 
  }
  handleSubmit = e => {
    e.preventDefault();
    this.setState({ value: this.nameInput.current.value})
  };
  render() {
    return (
        <form onSubmit={this.handleSubmit} />
          <input type="text" ref={this.nameInput}/>
        </form>
    );
  }
}

The same is the case when you try to focus on the input DOM node. You would need to make a minor update to the previous code snippet:

import React, { Component } from 'react';
class RefDemo extends Component {
  constructor(props) {
    super(props);
    this.nameInput = React.createRef();
  }
  handleSubmit = () => {
    this.nameInput.current.focus();
  };
  render() {
    return (
          <input type="text" ref={this.nameInput}/>
          <button onClick={this.handleSubmit}>
            focus!
          </button>
    );
  }
}

These are a few ways to use refs when building your React apps, however, more actions can be performed with refs depending on your particular use case. Like I mentioned before, refs can help you with a number of things, including reducing the number of re-renders in your components.

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

Refs in production

If you were building a production-grade application, the previous implementation will not be advised. That is because you’re not building the app The React Way. React requires that you communicate between components through props (not refs). That is what makes React, React.

A production-ready React app with the previous functionality will take a different approach. For example, when a component hosts an input element like we do in the previous implementation, React will expect you to set up an event handler to track changes to that input element.

That way, when a user types a character in the input field, the event handler will fire and update your state with the new value. Consider this:

import React from 'react';
class RefDemo extends React.Component {
    state = {
      inputValue: ""
    }
    handleChange = (e) => {
      this.setState({
        inputValue: e.target.value
      })
    }
    render() {
        const { inputValue } = this.state
        return (
          <div>
            <input value={inputValue} onChange={this.handleChange}} />
          </div>
       )
  }
}

The change to the state will cause the parent component to re-render itself, along with the input element with the new value. Notice from the implementation above that whenever the input value changes, the handler gets called. The handler then calls setState({ }) which, in turn, re-renders the component. This is the React way, and this is the expected implementation for React applications in production.

Why you should use refs sparingly

That said, here are some more reasons why you should use refs sparingly.

It breaks encapsulation

React requires that you only communicate between components through props. Refs suggest that you can communicate with other components using the ref attribute. This would get the information to the desired destination, however, you’ll lose data-driven actions in your application since refs won’t ensure data synchronization. State will not update and components will not re-render. Changes in the DOM are not tracked by the application state which of course breaks encapsulation.

It doesn’t follow the React pattern

React has a specified thought patter, a way to think when building React applications. It specifies that you control every piece of the application UI with state and component hierarchy. Using React refs to pass data around goes against the React thought pattern. React is, by design, state-driven. This means that each component is stateful. It has different states (think different UI presentations) and we can change that state to change how the UI looks, not necessarily just when an event occurs.

It encourages event-driven development

React is data-driven by design. Using React refs encourages you to update your application UI with respect to events rather than with respect to changes in data. Using React refs, we can update the application UI when an event occurs (user clicks on something). However, React prefers state-driven implementations where every component is stateful and can change that state to modify how the UI looks.

Tends to override React provided APIs

React provides native API’s for certain functionalities like conditional CSS classes, conditional rendering, etc. Building React applications with Refs will affect the way you think about React applications and have you overlooking these native API’s and instead promote using refs to implement those functionalities. These features (which React provides native API’s for) should not be implemented by using native DOM elements as it can be handled fully within the React API. That said, there are certain things you can’t do purely in React (like focusing an input programmatically) which is where refs should come in.

Possible alternatives to refs and suggestions

  • Use state to control all app UI updates
  • Use props to handle component hierarchical relationships
  • Use Redux when props chains get messy

Conclusion

In this post, we talked about the React refs and how to use them. Most importantly we’ve explained its shortcomings.

A general rule of thumb is to consider how possible it’ll be to do exactly what you want to do with props and state before thinking about refs. If you don’t find a reasonable implementation, only then should you consider using refs. Personally, I use refs only when I need to call specific functions on DOM elements, with focus() being by far the most common usage in my applications.

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

Peter Ekene Eze Learn, Apply, Share

Leave a Reply