Master React Ref: A Handbook for React Developers

Everything you need to know about React Ref: from basics to advanced techniques.

Brandon Evans
Bits and Pieces

--

Photo by Boxed Water Is Better on Unsplash

Refs are a powerful feature in React that allows you to access and manipulate DOM elements or store mutable values. In this article, we will explore the concept of refs in React and provide you with a detailed understanding of how to use them effectively in your applications.

Understanding React Refs

Refs in React are references to either DOM elements or mutable values. In the past, refs were mainly used for interacting with the DOM, but with the introduction of React Hooks, their usage has expanded to encompass other purposes as well. A ref, at its core, is simply a reference to something.

Let’s start with an example of using a ref to track the state of a button click:

import React, { useRef } from 'react';

const ButtonComponent = () => {
const buttonRef = useRef(false);
const handleClick = () => {
buttonRef.current = true;
};
return (
<div>
<button onClick={handleClick}>Click Me</button>
{buttonRef.current && <p>Button clicked!</p>}
</div>
);
};

In this example, we use the useRef Hook to create a ref object buttonRef and initialize it with the value false. When the button is clicked, the handleClick function updates the ref's current property to true. We can then conditionally render a message based on the value of the ref.

It’s important to note that updating a ref’s value does not trigger a re-render of the component. Refs are not meant to replace state for re-rendering components. Instead, they are useful when you need to track state without causing a re-render.

Using Refs as Instance Variables

One common use case for refs is to serve as instance variables in function components. They allow you to track some form of state that should not trigger a re-render. Let’s consider an example where we want to determine if a component has been rendered for the first time or if it has been re-rendered:

import React, { useEffect, useRef } from 'react';

const Component = () => {
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
console.log('Component re-rendered');
}
});
return <p>Component content</p>;
};

In this example, we initialize the ref’s current property to true because we assume that the component starts with its first render. Then, in the useEffect Hook, we update the ref's value after the initial render. This allows us to differentiate between the first render and subsequent re-renders.

By leveraging this technique, we can create an useEffect Hook that only runs its logic for every component update, excluding the initial render. This can be a valuable feature in certain scenarios.

Remember, using refs as instance variables is not a commonly used pattern and is typically not necessary. However, it can be a useful tool in specific cases where you need to track state without triggering a re-render.

Refs and the DOM

Now, let’s dive into the speciality of refs in React: the DOM. Most often, you will use React’s ref feature when you need to interact with HTML elements. While React promotes a declarative approach, there are cases where imperative access to the DOM is required. Refs allow you tointeract with the DOM imperatively.

Let’s take a look at an example of using a ref to interact with a DOM element:

import React, { useRef } from 'react';

const TextInput = () => {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus();
};
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
};

In this example, we create a ref using the useRef Hook and initialize it with null. We then assign this ref to the ref attribute of the <input> element. Now, the inputRef.current property holds a reference to the actual DOM node of the input element.

When the button is clicked, the handleFocus function is called, which uses the focus() method on the DOM node referenced by inputRef.current. This sets the focus on the input element, allowing the user to start typing immediately.

This demonstrates how we can use refs to interact with the API of HTML elements in an imperative manner, which is sometimes necessary for specific use cases.

Another common use case for refs is reading values from DOM nodes. Let’s say we want to display the width of an element as the page title:

import React, { useEffect, useRef } from 'react';
const ElementSize = () => {
const elementRef = useRef(null);
useEffect(() => {
const element = elementRef.current;
const width = element.offsetWidth;
document.title = `Element Width: ${width}px`;
}, []);
return <div ref={elementRef}>Resizable Element</div>;
};

In this example, we create a ref using the useRef Hook and assign it to the ref attribute of the <div> element. Inside the useEffect Hook, we access the DOM node using elementRef.current and retrieve its width using offsetWidth. We then set the page title to include the width value.

By using the empty dependency array [], we ensure that this effect runs only once, after the initial render. This way, we obtain the width of the element and display it as the page title.

If we wanted to update the element’s size whenever a specific state variable changes, we can include that variable in the dependency array:

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

const ElementSize = () => {
const elementRef = useRef(null);
const [text, setText] = useState('');
useEffect(() => {
const element = elementRef.current;
const width = element.offsetWidth;
document.title = `Element Width: ${width}px`;
}, [text]);
return (
<div ref={elementRef}>
<input type="text" value={text} onChange={e => setText(e.target.value)} />
</div>
);
};

In this updated example, we introduce a state variable text and an input element that modifies its value. By including text in the dependency array of the useEffect Hook, we ensure that the effect runs not only after the initial render but also whenever the text state variable changes. This allows us to update the element's size and reflect it in the page title as the user types.

So far, we have been using the useEffect Hook to work with the ref object. However, we can achieve the same result using a callback ref.

Callback Refs

Callback refs provide an alternative approach to working with refs, eliminating the need for the useEffect and useRef Hooks. With a callback ref, you can access the DOM node on every render without the use of additional hooks:

import React, { useRef } from 'react';

const TextInput = () => {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus();
};
const setRef = node => {
inputRef.current = node;
};
return (
<div>
<input type="text" ref={setRef} />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
};

In this example, we define a callback ref function setRef that takes a node as an argument. Inside the function, we assign the node to the current property of the inputRef object. By passing setRef as the ref attribute to the <input> element, React will call this function with the actual DOM node whenever it is rendered.

With callback refs, you have access to the DOM node directly on every render, which can be useful in certain scenarios. If you only want the callback ref to be called on the initial render, you can wrap it with the useCallback Hook:

import React, { useCallback, useRef } from 'react';

const TextInput = () => {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus();
};
const setRef = useCallback(node => {
inputRef.current = node;
}, []);
return (
<div>
<input type="text" ref={setRef} />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
};

In this updated example, we wrap the setRef callback in the useCallback Hook and provide an empty dependency array []. This ensures that the callback is only created once, during the initial render. The inputRef.current value will persist across renders without the need for additional dependencies.

It’s worth noting that you can make the dependency array more specific if you want the callback function to be called only when certain dependencies change, along with the initial render.

Refs for Read/Write Operations

So far, we have primarily focused on using refs for reading values from DOM nodes. However, refs can also be used for write operations, allowing you to modify the referenced DOM nodes.

Let’s consider an example where we use a ref to apply a custom style to a DOM element:

import React, { useRef } from 'react';

const ColoredText = () => {
const textRef = useRef(null);
const handleColorChange = () => {
textRef.current.style.color = 'red';
};
return (
<div>
<p ref={textRef}>This is a paragraph</p>
<button onClick={handleColorChange}>Change Color</button>
</div>
);
};

In this example, we create a ref called textRef using the useRef Hook and assign it to the ref attribute of the <p> element. When the button is clicked, the handleColorChange function is triggered, which accesses the DOM node through textRef.current and modifies its style.color property to change the text color to red.

It’s important to note that directly manipulating the DOM in this manner goes against React’s declarative nature. In most cases, it is recommended to use React’s state management (e.g., the useState Hook) to control and update styles or other attributes. However, there may be rare instances where directly manipulating the DOM through refs can be beneficial for performance reasons or when interacting with third-party libraries that rely on imperative DOM manipulation.

While it is possible to manage state using refs, as shown in the previous example, it is generally not recommended. React provides powerful state management mechanisms, such as the useState Hook, which are designed to handle state updates and trigger re-renders efficiently.

💡 Note: If multiple components need access to the same ref, you can use a Context provider to manage the ref, and wrap your components with it. Using Bit, you can then export/publish the provider, making it easier to share across different projects.

Learn more here:

Conclusion

In this comprehensive guide, we have explored the world of React refs. We started by understanding the concept of refs as references to either DOM elements or mutable values. We learned how to use the useRef Hook to create and manage refs in functional components. We saw how refs can serve as instance variables to track state without triggering re-renders.

Furthermore, we delved into the interaction between refs and the DOM, where we leveraged refs to access and manipulate HTML elements imperatively. We covered scenarios such as reading values from DOM nodes, updating styles, and performing other read/write operations.

We also explored the alternative approach of using callback refs, which provide direct access to the DOM node on every render. We saw how to optimize callback refs using the useCallback Hook to prevent unnecessary re-creation of the callback function.

Finally, we emphasized the importance of using refs appropriately and cautioned against excessive reliance on imperative DOM manipulation. React’s declarative nature and its state management mechanisms should be leveraged whenever possible to ensure predictable and maintainable code.

Keep exploring, experimenting, and building amazing applications with React!

Build Apps with reusable components, just like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo

--

--