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.
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:
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.
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.
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.
That said, here are some more reasons why you should use refs sparingly.
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.
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.
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.
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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]