The `ref` system available in [React](https://react.dev/) enables you, for example, to access and modify traditional [DOM nodes](https://developer.mozilla.org/en-US/docs/Glossary/Node/DOM) or other components (and this means you don’t use the regular data flow). This is so helpful, especially when you want to deal with some particular case, such as focusing on input fields, animations, or just handling elements. However, issues arise when components are more reusable and nested because it’s hard to pass `ref`s through intermediate layers of components, but this article will show you how to deal with this.
Session Replay for Developers
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data.
Happy debugging! Try using OpenReplay today.
The forwardRef
function allows ref
s to be passed from parent to child without a component abstraction break. This process makes the component composition seem more efficient and clean, especially when some elements or methods are combined, which need access to the underlying DOM. In this article, we will help you understand this function, what it does when you should use it, and the best practices to apply to make it even more effective on your apps.
This function passes ref
s through a component to access a child component or even a DOM element directly. It is most applicable where the component's structure is complex, such as when a parent component wishes to manage or interact with other components far below the component tree. It is useful in filling this gap without protruding the encapsulation of components.
By default, the ref
attribute is used to pass a DOM element directly to a component instance. However, if there’s a reusable component that doesn’t expose the inner DOM directly, it will be hard to pass the ref
. The function aims to solve this, as there is freedom for components to “forward” the ref
from a parent to a certain DOM node or child component.
Why forwardRef
is important
If the function is missing, the ref would only go as far as the InputField component, preventing the parent from getting a hold of the input element inside. It helps to overcome all those issues and prevents the parent component from losing the ability to strictly manipulate the nested DOM nodes using forwardRef. This can be an enormous advantage when developing reusable and controlled components like a specific input field in which the parent must gain control over a particular action such as focusing or changing the input field's value. This makes the whole system or component composition to become more flexible.
How forwardRef
Works
When a component is wrapped with this function, the ref
from the parent is stored, and it is passed to the child component as the second argument. This process enables you to work with the child elements as though you're working directly from the parent. For instance, when you want an input field to accept focus on a button click, the function allows passing the input ref
to the parent component. With this, a parent does not need direct access to trigger actions on the input. For further analysis, you can check out the React forwardRef
documentation.
Using refs Directly or Using forwardRef
?
You probably guessed that directly using ref
s within a component is just fine; what you need to manage is the elements within the same component. However, one disadvantage is that it becomes increasingly difficult to manage child components without forwardRef
, and thus, large application flexibility can be a problem.
In general, you can depict that the function acts as a brush to forward the ref
from the parent to the target and sets a direct means of communication between elements within the nested structures. This makes the construction of higher-order components simpler, and it also reuses logic in intricate component frameworks, making code easier to maintain.
forwardRef
Mechanics
The function takes a component as an argument and returns a new component that can receive ref
from its parent component. This makes it possible for the ref
to be rendered towards a component in the child so that the parent can in real-time interact with it. For instance, we use the function to wrap a custom InputField
component, which includes an HTML input element; thus, the parent component can directly access the DOM node of the input.
import React, { forwardRef } from "react";
// Define the InputField component and wrap it with forwardRef
const InputField = forwardRef((props, ref) => {
return <input type="text" ref={ref} {...props} />;
});
In the above, the forwardRef
function expects a single argument: a function component. Now, this said function gets two parameters:
props
: Here, you will see all that has been passed toInputField
as props by the parent component. For this,{... props}
passes all props to the<input>
element above.ref
: This parameter is originally used with input element inInputField
and in a basic sense that it points to the elements. Onceref
is passed to the<input>
element (ref={ref}
), the parent component has complete control over this child element.
Using the function as done above helps to directly make ref from the parent link to the HTML input element within a specified field.
Example Using a Parent Component
The forwardRef
is utilized when a parent wants to access a child component’s native DOM node. In this case, ParentComponent
uses it to access the input field on which it sets focus. Now, let’s have an example of how a parent can use InputField
with the function to control an input element.
import React, { useRef } from "react";
const ParentComponent = () => {
// Create a ref using useRef
const inputRef = useRef(null);
// Function to focus the input
const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<InputField ref={inputRef} placeholder="Type something here..." />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
};
In the above:
- The hook
useRef
creates a variableref
(inputRef
) in theParentComponent
component. - This
ref
is then forwarded toInputField
so that when the buttononClick
handler is invoked, it callshandleFocus
. This directs the input element to focus.
The component returns an input with the placeholder “Type something here…” Below the input, there is a button with the label “Focus Input.” When pressed, the button brings the input field into focus through the handleFocus
function. We will give more explanation and proper output later in the article.
Common Use Cases for forwardRef
The role of this function manifests in several situations of the internal component elements manipulation by the parent one. The above approach is good for creating complicated UI elements and reusable ones that can benefit from the direct workflow with certain child elements. In this section let’s take a closer look at the common use cases.
Custom Input Components
It is also commonly used in several pieces of custom input, which may include text fields, dropdowns, or checkboxes, among others. If you intend to build reusable input components, they are often used to work in these fields. For instance, focusing on an input when a form is loaded or erasing a field as soon as a submission is made. The function enables a parent component to get a direct handle on the input component, making it relatively easy to manage it. Now, let’s look at an example.
// CustomInput component with forwardRef
const CustomInput = React.forwardRef((props, ref) => (
<input type="text" ref={ref} {...props} />
));
Above, we see the parent using CustomInput
and then working by placing focus on the element or clearing the input using the ref
.
Reusable UI Components
The function is helpful when creating reusable UI components, such as buttons, modals, or forms, where the parent would want to perform DOM manipulations. For example, let’s assume a modal component that frequently needs control over operations such as opening or closing the modal or concentrating on certain fields in the modal alone. forwardRef
works in a way that if you expose the modal’s key elements through it, it ensures that a parent component can control these actions. Let's take the example below.
// ReusableModal component with forwardRef
const ReusableModal = React.forwardRef((props, ref) => (
<div ref={ref} className="modal">
{props.children}
</div>
));
What the code does:
ReusableModal
returns a <div>
that displays whatever child element passes through props.children
. React.forwardRef
, allows the component to receive a ref
passed from the parent, which is then applied to this <div>
.
How it works:
- Due to the function, the parent can pass a
ref
toReusableModal
, which points to the<div>
of the modal. This leaves the control of the modal's display and other options in the hands of the parent.
Using props.children
allows one to pass any content through the component, enabling it to be reusable for other uses as well.
With the help of the function, the Modal’s key <div>
is passed to the parent component. This enables you to perform direct DOM manipulations as well as opening, closing, or focusing without having impacts on the component’s inside code.
Let’s see another example that describes how the parent might use this component.
const Parent = () => {
const modalRef = React.useRef();
const openModal = () => {
modalRef.current.style.display = "block"; // Example to show modal
};
return (
<div>
<ReusableModal ref={modalRef}>
<p>This is the modal content</p>
</ReusableModal>
<button onClick={openModal}>Open Modal</button>
</div>
);
};
Code explanation:
To directly access the modal’s DOM node,
modalRef
is generated inParent
usinguseRef()
.The
openModal
function setsmodalRef.current.style.display
toblock,
thus making the modal visible. This function is triggered when the “Open Modal” button is clicked, which alters the modal's visibility directly using the ‘ref’.
And so the Parent
component generates a button labeled “Open Modal” followed by an instance of ReusableModal
, which carries the text “This is the modal content.” This modal is initially hidden and can only be visible when clicking the “Open Modal” button. It will look something like the one below.
Library and Framework Interoperability
A lot of third-party libraries and frameworks rely on ref
s to get access to DOM elements. There are libraries like validators, drag-and-drop frameworks, or DOM measurement tools, which might need to access certain elements on your components directly. forwardRef
can be used because it is easy to integrate with these libraries by exposing the necessary refs to elements within your components.
For instance, we have a custom button using a component called DraggableButton
, and we want to make it draggable using the third-party library that we will name dragLibrary
. This library will now demand that you reference the DOM element you want to work with to make it draggable. You can then use the function to pass a ref
to the button through which the parent component can link to the drag-n-drop features from dragLibrary
.
import React, { forwardRef, useRef, useEffect } from "react";
// Hypothetical drag-and-drop library named 'dragLibrary'
import { enableDrag } from "dragLibrary";
// DraggableButton component wrapped with forwardRef
const DraggableButton = forwardRef((props, ref) => (
<button ref={ref} {...props} className="draggable-button">
{props.children}
</button>
));
In the above, the DraggableButton
component is wrapped with the function that allows the parent to pass a ref
to the <button>
element. By using ref={ref}
on the <button>
, you ensure that the DOM Element will be accessible to your parent component.
Here is an example of using ParentComponent
alongside this ref
to integrate the button with dragLibrary
.
const ParentComponent = () => {
const buttonRef = useRef();
useEffect(() => {
// Use the drag-and-drop library's function `enableDrag` to make the button draggable
if (buttonRef.current) {
enableDrag(buttonRef.current); // Enables dragging using dragLibrary
}
}, []);
return <DraggableButton ref={buttonRef}>Drag Me</DraggableButton>;
};
Code explanation:
In the ParentComponent
above, we first use the useRef
to create buttonRef
, and then we pass this to our DraggableButton
component, which then forwards the ref
to the <button>
element. Once ParentComponent
has rendered, it calls the enableDrag
function from dragLibrary
, which enables the drag functionality on the "Drag Me" button. This makes the button draggable, allowing users to move it across the screen. The output should look like the one below.
Animation Libraries
Animation libraries come up with complex, high-performance animation by working directly in the DOM. forwardRef
helps in easily integrating components with animation libraries like Framer Motion
and GSAP
to enable DOM element manipulation. This process is useful to developers who plan to develop exciting and interesting UI interfaces whereby individual components shift based on scrolling or other actions.
For instance, you may have a component that animates an element on screen when it comes into the viewport. Using forwardRef
, you can expose the DOM node of that element. This enables the animation library to gain direct control.
// AnimatedBox component with forwardRef
const AnimatedBox = React.forwardRef((props, ref) => (
<div ref={ref} className="animated-box">
{props.children}
</div>
));
This AnimatedBox
above is a component that interacts with an animation library where the library might require interaction with the <div>
for applying animations. The ref
for the <div>
element above is exposed after implementing the function.
Now let’s take a closer look at how the AnimatedBox
can be used by a parent component that works with an animation library (GSAP for instance).
import { useRef, useEffect } from "react";
import gsap from "gsap";
const Parent = () => {
const boxRef = useRef();
useEffect(() => {
// Use GSAP to animate the box when it mounts
gsap.to(boxRef.current, { x: 100, duration: 2 });
}, []);
return (
<AnimatedBox ref={boxRef}>
<p>Animated content here</p>
</AnimatedBox>
);
};
Code explanation:
The Parent
component calls the useRef
hook to create boxRef
, which is then passed to the AnimatedBox
component. Within the useEffect
, we use GSAP to move the element with reference boxRef
100 pixels to the right for 2 seconds. The Parent
component renders AnimatedBox
containing an HTML <p>
tag that displays the text “Animated content here”. Due to the GSAP animation specified, this content moves horizontally when loaded. The output should look like the one below.
Best Practices for Using forwardRef
By the time you have implemented the React function, you are halfway through making your components more versatile. Still, some simple rules can make the code more maintainable while also keeping it simple for others to review. In this section, we will present you with a few things to bear in mind when using React.
Avoid Overusing Refs
As much as ref
s have been observed to be very helpful, try to avoid using them too much or the components become tightly coupled to the DOM elements. This effect complicates the preservation of the components. Its usage should be restricted to components that require direct interaction with the DOM, such as custom input components and animations, or when you want to work with third-party libraries. Instead, focus on props and states for most interactions as they are much easier to handle on React’s ecosystem.
Document Component Behaviour
One thing that should be pointed out about the function is that you should add good documentation to the component. Describe why a component uses it and what other developers should expect from the ref
. For example, you should note if a ref
gives you access to a certain element in DOM (e.g., input field) or certain functions in the component. This documentation is produced by inserting comments into your code at places where you deem them necessary. This assists other people on your current or future team in learning how to use a component and how it will behave.
Combine with useImperativeHandle
The useImperativeHandle
hook can be useful when controlling specific things to be exposed through the ref
. By default, the DOM element of an entire component is often exposed but you may only wish to expose selected methods or properties, then useImperativeHandle
provides the way to create your API to help to make your component’s ref
usage more safe and predictable.
const CustomInput = React.forwardRef((props, ref) => {
const inputRef = React.useRef();
React.useImperativeHandle(ref, () => ({
focusInput: () => inputRef.current.focus(),
}));
return <input ref={inputRef} {...props} />;
});
In the above, the parent component is only given access to focusInput
, not other properties in the input element. You can find broader context on this hook on React's useImperativeHandle
documentation.
Use Descriptive Variable Names
When using the function, you should ensure that you keep an efficient naming convention for the referee's distinguishability. You could provide names like ref
which will be bland, clueless, and generic. Or you could instead go with modalRef
or inputRef
, which at least tells what the reference is supposed to be used for. This naming method makes it easier for developers to appreciate your intention and the usage of the ref
in your component. Let’s take the naming style below for example.
const CustomButton = React.forwardRef((props, buttonRef) => (
<button ref={buttonRef} {...props}>
Click me
</button>
));
A name such as buttonRef
used above will be a better choice compared to simply using "ref
". This is because it can be easily seen that it references a button element.
Examples of Using forwardRef
Effectively
We have provided some examples of when you should use this function but how do you get the best out of it? In this section, we are going to show some practical examples of how you can use it to make flexible components.
Basic Input Component with forwardRef
As mentioned, basic input components are one of the more common use cases for this function. It enables you to build components that can be reused and invoked by the parent component with the DOM node of the input field, for example, to focus, clear, or check the validity of input fields. To do this, you will create a CustomInput
component that wraps a standard HTML input element. The function allows the parent component to pass in a ref
and attach to the <input>
element.
import React, { forwardRef } from "react";
// Create a reusable input component with `forwardRef`
const CustomInput = forwardRef((props, ref) => (
<input type="text" ref={ref} {...props} />
));
export default CustomInput;
CustomInput
uses forwardRef
to handle refs in the above. The ref
is the second argument (after props
) passed to the <input>
element. This is why the ref
attribute on <input>
allows parent components to gain a reference to the DOM elements to influence. And this input is very reusable and flexible because we are passing {…props}
.
The next logical question arises: how will a parent component be able to utilize our CustomInput
component? Let's take, for instance, the code example below if a button is clicked and you want the parent to focus on the input field.
import React, { useRef } from "react";
import CustomInput from "./CustomInput";
const FormComponent = () => {
// Create a ref for the CustomInput component
const inputRef = useRef();
// Function to focus the input field when called
const focusInput = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
{/* Use CustomInput and pass the ref */}
<CustomInput ref={inputRef} placeholder="Enter text here" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
};
export default FormComponent;
Code explanation:
We use useRef
to generate the ref named inputRef
using FormComponent
, which is then transmitted to CustomInput
. The focusInput
function gets triggered when the button is clicked. First, it checks if inputRef.current
is available, and if it is, it calls focus()
to set on the input field. The FormComponent
renders a CustomInput
with a placeholder "Enter text here" and a button saying "Focus Input." When a user clicks the button, it transfers the focus to the input box area so they can start typing immediately, like the output below.
Button Component With Animated Ref
Another example is to create a button component that needs to have an animation every time it is clicked. Here, the function provides access to the DOM node so you can use various animations and other visual effects.
import React, { forwardRef } from "react";
// Define an animated button component with `forwardRef`
const AnimatedButton = forwardRef((props, ref) => {
const handleClick = () => {
if (ref && ref.current) {
ref.current.classList.add("animate");
setTimeout(() => ref.current.classList.remove("animate"), 300);
}
};
return (
<button ref={ref} onClick={handleClick} {...props}>
{props.children}
</button>
);
});
export default AnimatedButton;
In the above case, a ref
is passed to AnimatedButton
through the forwardRef
attached to the <button>
element. An animation class is added using handleClick
for the button so that when clicked, the animation takes 300ms before it resets for the next click.
The parent component can then pass to a ref
to an AnimatedButton
which can trigger the animation click.
import React, { useRef } from "react";
import AnimatedButton from "./AnimatedButton";
const PageComponent = () => {
const buttonRef = useRef();
return (
<div>
<AnimatedButton ref={buttonRef}>Click Me</AnimatedButton>
</div>
);
};
export default PageComponent;
Code explanation:
In PageComponent
, it creates a ref (buttonRef
) using useRef
and passes it to the AnimatedButton
component. This ref can be used within AnimatedButton
to add animation or effects on the button element. An AnimatedButton
labeled “Click Me” is rendered by PageComponent
. If AnimatedButton
contains animations such as on-click or hover effects, they will be applied to the button once displayed. You can find an example of what this looks like below.
Integrating forwardRef
with Forms
When used with forms, it is valuable for developing input elements that could be managed through the parent form. For instance, we have pointed out how the function can focus or clear input fields when desirable. In this case, it allows for further control by specifying what the ref
passes to the parent through useImperativeHandle
. The first step usually involves defining a FormInput
element. This component will use the function to accept a ref
and useImperativeHandle
to pass specific functions (like clearing & focusing the input to the parent form).
import React, { forwardRef, useImperativeHandle, useRef } from "react";
// Define a form input component with focus and clear functionalities
const FormInput = forwardRef((props, ref) => {
const inputRef = useRef();
// Define the functionality exposed to the parent via `useImperativeHandle`
useImperativeHandle(ref, () => ({
focusInput: () => inputRef.current.focus(),
clearInput: () => (inputRef.current.value = ""),
}));
return <input ref={inputRef} {...props} />;
});
export default FormInput;
The FormInput
above surrounds the <input>
element and accepts the ref
via forwardRef
. For the <input>
element in the component, a reference (inputRef
) is assigned with the help of useRef
. In turn, the useImperativeHandle
modifies this reference to expose only two functions (focusInput
and clearinput
) to the parent component.
And now let’s try to use FormInput
in a parent form component. Here, the parents will control the input (focusing and clearing) with the exposed function by useImperativeHandle
.
import React, { useRef } from "react";
import FormInput from "./FormInput";
const FormComponent = () => {
// Create a ref for the FormInput component
const inputRef = useRef();
// Function to focus the input field
const handleFocus = () => {
inputRef.current.focusInput();
};
// Function to clear the input field
const handleClear = () => {
inputRef.current.clearInput();
};
return (
<div>
<FormInput ref={inputRef} placeholder="Enter name" />
<button onClick={handleFocus}>Focus Input</button>
<button onClick={handleClear}>Clear Input</button>
</div>
);
};
export default FormComponent;
Code explanation:
In FormComponent
above, inputRef
is passed to FormInput
as a ref
. This is so that FormComponent
can call the defined input field methods in useImperativeHandle
. handleFocus
calls focusInput
, which, whenever the button “Focus Input” is clicked, focuses the input field. On the other hand, handleClear
clears the input field using clearInput
, any time the “Clear Input” button is clicked, just like the below.
Conclusion
Creating clean and reusable components in React that provide controlled access to DOM is achievable using forwardRef
. If used properly, it will make the component more versatile and let parents span directly to child elements. If you're dealing with essentials such as custom inputs, animated buttons, or complex interactions on forms, it will be completely ideal to use it. However, it is worth noting that simply implementing this function is not the only aspect to consider. You should consider the best practices concerning its usage to improve your code and reduce redundancy in larger applications. Lastly, you can check React's referencing values with refs
documentation for further information on the function's relationship with ref
.
Top comments (0)