DEV Community

Cover image for ForwardRef in React: When and How to use it
OpenReplay Tech Blog
OpenReplay Tech Blog

Posted on

ForwardRef in React: When and How to use it

by Uduak Udohudoh

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.

OpenReplay

Happy debugging! Try using OpenReplay today.


The forwardRef function allows refs 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 refs 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 refs 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} />;
});
Enter fullscreen mode Exit fullscreen mode

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 to InputField 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 in InputField and in a basic sense that it points to the elements. Once ref 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>
 );
};
Enter fullscreen mode Exit fullscreen mode

In the above:

  • The hook useRef creates a variable ref (inputRef) in the ParentComponent component.
  • This ref is then forwarded to InputField so that when the button onClick handler is invoked, it calls handleFocus. 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} />
));
Enter fullscreen mode Exit fullscreen mode

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>
));
Enter fullscreen mode Exit fullscreen mode

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 to ReusableModal, 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>
 );
};
Enter fullscreen mode Exit fullscreen mode

Code explanation:

  • To directly access the modal’s DOM node, modalRef is generated in Parent using useRef().

  • The openModal function sets modalRef.current.style.display to block, 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.

GIF_20241113_123655_080

Library and Framework Interoperability

A lot of third-party libraries and frameworks rely on refs 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>
));
Enter fullscreen mode Exit fullscreen mode

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>;
};
Enter fullscreen mode Exit fullscreen mode

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.

GIF_20241113_120220_516

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>
));
Enter fullscreen mode Exit fullscreen mode

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>
 );
};
Enter fullscreen mode Exit fullscreen mode

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

GIF_20241113_123017_894

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 refs 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} />;
});
Enter fullscreen mode Exit fullscreen mode

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>
));
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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.

GIF_20241113_123124_735

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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.

GIF_20241113_123236_828

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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.

GIF_20241113_152435_601

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)