Skip to main content
Ref Forwarding with React forwardRef
20 min read

Ref Forwarding with React forwardRef

Introduction

This post is about the forwardRef() utility function in React. We discuss in detail how refs work in React, why they need to be forwarded in functional components, and how forwardRef() is used to manipulate child DOM elements.

React forwardRef() is a utility function that helps convey refs from a parent component to a child functional component. In a functional component in React, forwarding a parent's refs is important as child functional components by default do not have a ref attribute and do not accept the parent's ref as a prop. ref forwarding from parent to a child functional component, among other uses, especially plays important role in manipulating DOM nodes in the child.

In this post, we explore what entails ref forwarding with React.forwardRef() and how this function is used in manipulating DOM elements as well as in higher-order components in React.

We first spend time to understand what the ref attribute in a React component stands for, and how to initialize a ref value in a parent component using the useRef() hook and the React.createRef() method. We expound on why functional React components fail to accept a ref prop from its parent and demonstrate with examples how the forwardRef() utility addresses this shortcoming. With a <VideoPlayer /> example, we examine the internals of how ref forwarding works with the participation of a parent component that initializes a ref handle, a child functional component that acquires it and internally ties one of its DOM node to that handle by setting its ref attribute.

In the latter half of the post, we consider an example of ref forwarding in higher-order components and how forwardRef() is used in building reusable components in robust UI systems like Shadcn/ui. We also cover the benefits of using forwardRef(), and some of the associated pitfalls and best practices centered around ref forwarding.

Steps we'll cover in this post:

What is React forwardRef?

React.forwardRef() is a React utility function that allows ref values from a parent component to be availed to a child functional component. The refs are first created using useRef() or createRef() inside the parent. They are then forwarded and picked to manipulate a DOM node rendered inside the child JSX.

React.forwardRef() returns a component that can accept a ref prop, something the original functional component is not capable of doing. In other words, React forwardRef() enables a functional component to accept a ref prop and forward it to its internal JSX.

For example, in the following code, we are using the forwardRef() API for implementing focus on an <input /> element:

import React, { useRef } from "react";

export function Form() {
const inputRef = useRef(null);
const handleClick = () => inputRef.current.focus();

return (
<div className="form">
<Input label="Title" ref={inputRef} />
<button className="button" onClick={handleClick}>
Focus Title Field
</button>
</div>
);
}

export const Input = React.forwardRef((props, inputRef) => {
const { label, ...otherProps } = props;
return (
<div className="form-control">
<label className="form-label">{label}</label>
<input className="form-field" {...otherProps} ref={inputRef} />
</div>
);
});

What are refs in React?

refs in React are understood in two levels: first, the JSX ref attribute and secondly, ref values initialized by an owner using the useRef() hook or the createRef() method on React.

The JSX ref Attribute

A ref in React is an attribute assigned to a JSX node. The ref attribute does not exist in HTML elements and is specific to JSX and React. Thus a ref attribute can be set on JSX elements. It can also be set on a React class component node:

<input ref={refValue}> // ref attribute set on JSX elements

// OR
<AClassComponent ref={refValue} /> // ref set on a class component
[TIP: A ref value ties the current node to the value's owner]

The JSX ref attribute placed on a target component or element gives the refValue owner access to the JSX node. The refValue is an object with a current property. The node is assigned to the current property and can be accessed by the owner via refValue.current:

const refValue = {
current: HTMLElement,
};

const targetNode = refValue.current;

Then intended actions such as event handlers can be implemented on the node as necessary.

ref Values in React

Secondly, ref values are initialized by an owner. They can be initialized using the useRef() hook or the React.createRef() method. A ref value is typically initialized or possessed by a parent component. When intended for a target JSX element, it can then get assigned to the ref attribute of the JSX node. Setting a ref value to the ref attribute of a JSX node allows the parent component to control it via its DOM APIs.

For example, in the following <Input /> component, inputRef is set as a ref value of the <input /> element:

import { useRef } from "react";

export const Input = () => {
const inputRef = useRef(null);

const handleClick = () => inputRef.current.focus();

return (
<div>
<div className="form-control">
<label className="form-label">Title</label>
<input className="form-field" ref={inputRef} />
</div>
<button className="button" onClick={handleClick}>
Focus Title Field
</button>
</div>
);
};

The ref value is then used to access and manipulate the <input /> node, i.e. making it focus with the handleClick function.

[Refs are React ways of getting a DOM element]

React refs offer an escape hatch to get a handle over a child JSX or DOM nodes. They are React ways of getting a DOM node, something we achieve with document.getElementById() in plain JavaScript.

Apart from storing a reference to a JSX or DOM node, a ref value can be used to store strings or numbers data as well. They can be used for values that represent anything other than component / application state.

[WARNING: Do not use refs for states]

It is strongly suggested not to use refs for storing component states. This is because refs do not trigger a re-render. And thus are not updated in the UI.

What Does useRef() Do in React ?

The useRef() hook is used to initialize a ref's value (such as inputRef above) to be possessed by an owner component. The useRef() hook is used for initializing ref values, particularly in a functional component. As in the <Input /> component above:

import { useRef } from "react";

export function Input() {

const inputRef = useRef(null); // Declaring a ref value

return (
// ...
);
};

It is equivalent to the React.createRef() method that is used for initializing a ref value in class components.

What Does React.createRef() Do in a Class Component

The React.createRef() method is used to initialize a ref value inside a class component. If we were to use a class component for implementing the same above focus functionality, it would be like this:

import React, { Component } from "react";

export class Input extends Component {
constructor(props) {
super(props);
this.inputClassRef = React.createRef();
}

handleClick = () => {
this.inputClassRef.current.focus();
};

render() {
return (
<div>
<div className="form-control">
<label className="form-label">Title</label>
<input className="form-field" ref={this.inputClassRef} />
</div>
<button className="button" onClick={this.handleClick}>
Focus Title Field
</button>
</div>
);
}
}

Notice, that we have used the React.createRef() method this time to initialize a value for the ref attribute. This is so because the <Input /> component now is a class component.

It is important to note that, in both the examples above, we haven't applied any ref forwarding. This is because, the <input /> element is hosted as a direct child of the ref value owner, so no forwarding is necessary.

ref forwarding is needed when the target node by default does not get passed the owner's refs, for example when rendered inside a child functional component.

Why Ref Forwarding is Needed in React Functional Components

React adds the ref attribute out-of-the-box in JSX shadow DOM nodes. This happens by setting the ref attribute to DOM elements during component mount. The ref attribute can be custom-added to React class components as well. Setting a ref attribute is possible because class components and DOM elements have instances.

However, React functional components do not have instances since they are JavaScript functions. So, a React functional component cannot be assigned a ref attribute. This makes a ref value owner associate it to child functional components and any JSX node within them.

To overcome this, a child functional component needs to be enabled to accept a ref props and forward the ref values of the owner (typically a parent component). This is done by wrapping the functional component with the React.forwardRef() function. Doing so gives us an enhanced component that can be passed a ref prop.

How React.forwardRef() Works

React.forwardRef() accepts a render function as its argument. The render function is always a React functional component:

const EnhancedComponent = React.forwardRef(render);

Wrapping a child functional component with React.forwardRef() returns an enhanced component that can be passed a ref prop and conveys the accepted ref values to the render function's closure. This means the ultimate job of the React.forwardRef() function is to avail the parent's ref values to the child functional component:

import React from "react";

export const Input = React.forwardRef((props, inputRef) => {
// Use inputRef to set ref attribute of DOM node

const { label, ...otherProps } = props;

return (
<div>
<div className="form-control">
<label className="form-label">{label}</label>
<input className="form-field" {...otherProps} ref={inputRef} />
</div>
</div>
);
});

And then inside the component's JSX, a target node (the <input /> element above) gets associated with the parent by setting the target's ref attribute to the forwarded value (inputRef).

In other words, a parent node initializes a ref value (such as inputRef) somewhere else. A child functional component is wrapped with the React.forwardRef() function, which conveys the ref value to the function's lexical context. The functional component picks the ref value and internally associates a subnode of its own by setting the latter's ref attribute to the value. The functional component also accepts its usual props object.

The returned component then is equipped to be passed a ref prop when rendered inside the parent:

import { useRef } from "react";

export function Form() {
const inputRef = useRef(null);
const handleClick = () => inputRef.current.focus();

return (
<div>
<Input label="Title" ref={inputRef} /> // The enhanced component now accepts
a `ref` prop
<button className="button" onClick={handleClick}>
Focus Input Field
</button>
</div>
);
}

Anatomy of Ref Forwarding with React.forwardRef(): A Video Player Example

In this section, we elaborate on different aspects of forwarding a ref with React.forwardRef(). We consider a <VideoPlayer /> component, that acts as the parent of a child functional component which returns a <Video /> component after being wrapped with React.forwardRef().

The Parent Component

React ref forwarding involves a parent component that is the owner of the ref value. The parent in this example is the <VideoPlayer /> component, which initializes a videoRef. It looks like this:

Show VideoPlayer component code
import React from "react";
import { Video } from "./video";

export function VideoPlayer({ source }) {
const videoRef = React.useRef(null);

const playVideo = () => videoRef.current.play();
const pauseVideo = () => videoRef.current.pause();

return (
<div className="video-player">
<Video
source={source}
ref={videoRef}
/>
<div className="video-controls">
<button
className="video-button"
onClick={playVideo}
>
Play
</button>
<button
className="video-button"
onClick={pauseVideo}
>
Pause
</button>
</div>
</div>
);
}

Using useRef() with React.forwardRef()

Since we are using a functional component for the parent, we are initializing the videoRef with useRef() hook:

const videoRef = React.useRef(null);

Pass ref Prop of Child Functional Component

Inside JSX, we have to then pass the ref prop of the child functional component (<Video />) to the initialized value:

return (
<Video
source={source}
ref={videoRef}
/>
);

We expect this to be possible because a ref prop will be enabled on <Video /> thanks to React.forwardRef(). Normally, the ref prop here would be non-sense if <Video /> is a plain functional component. Since we'll be wrapping <Video /> with React.forwardRef(), ref gets recognized as a prop being forwarded internally.

Controlling ref Target from the Parent

We are also defining two event handlers for controlling the target DOM node (a <video> element):

const playVideo = () => videoRef.current.play();
const pauseVideo = () => videoRef.current.pause();

The necessity and perhaps the significance of ref forwarding is that we want to control the target from within the parent, without passing the event handlers down the DOM tree. Instead, we'd just pass along the ref value.

This way of control segregates the control-related nodes up the DOM tree from the target node further down. And helps keep the control nodes inside the parent component:

// Control remains inside the parent <VideoPlayer /> component

<div className="video-controls">
<button
className="video-button"
onClick={playVideo}
>
Play
</button>
<button
className="video-button"
onClick={pauseVideo}
>
Pause
</button>
</div>

The Child Functional Component with React.forwardRef()

The most important part of React ref forwarding entails conveying the ref attribute that is passed to the child functional component (<Video /> inside <VideoPlayer /> we saw above). In other words, the child component has to be wrapped with React.forwardRef(). As in the <Video /> component below:

import React from "react";

export const Video = React.forwardRef((props, videoRef) => {
const { source, ...otherProps } = props;

return (
<video
{...otherProps}
ref={videoRef}
width="640"
height="360"
controls
controlslist="play timeline volume"
>
<source src={source} type="video/mp4" />
</video>
);
});

Otherwise, ref gets passed to the child as a regular prop, and its current property is unintelligible.

Notice, the ref value (videoRef) is forwarded as a separate argument to the render argument of React.forwardRef(), and we can access it as so.

React Ref Forwarding: Setting ref Attribute in a Target DOM Element

We have to then set the ref attribute of the target DOM node to the forwarded videoRef value:

<video {...otherProps} ref={videoRef}></video>

Setting the ref attribute of the target node allows it to be controlled from the parent component. With this, now, we can trigger playVideo and pauseVideo functions from the Play and Pause buttons that are rendered inside the parent <VideoPlayer /> component.

Forwarding Refs from a React Class Component

Notice the parent <VideoPlayer /> component. It is currently a functional component. However, the parent component involved in ref forwarding does not have to be a functional component.

[NOTE: The child must be a functional component, the parent can be a class]

The child component must be functional as React.forwardRef() is intended to legitimize the ref attribute in the child functional component. But the parent can be a class component also.

Below, we have a React class version of the parent <VideoPlayer /> component that uses React.createRef() method to initialize videoRef, and all classy stuff we don't use in functional React:

Show VideoPlayer class component code
import React, { Component } from "react";

export default class VideoPlayer extends Component {
constructor(props) {
super(props);
this.videoRef = React.createRef();
}

playVideo = () => {
this.videoRef?.current?.play();
};
pauseVideo = () => {
this.videoRef?.current?.pause();
};

render() {
const { source } = this.props;

return (
<div className="video-player">
<Video
source={source}
ref={this?.videoRef}
/>
<div className="video-controls">
<button
className="video-button"
onClick={this?.playVideo}
>
Play
</button>
<button
className="video-button"
onClick={this?.pauseVideo}
>
Pause
</button>
</div>
</div>
);
}
}

React forwardRef: Advanced Examples

In this section, we consider advanced use cases that utilize React.forwardRef(). We illustrate with an example, how to compose enhanced higher-order components in combination React.forwardRef(). We also discuss how ref forwarding with React.forwardRef() underpins mission-critical DOM manipulation mechanisms in robust UI libraries like Shadcn/ui and React Hook Form.

Using React.forwardRef() in Higher Order Components

Higher-order components, or HOCs, are a React pattern for deriving enhanced versions from an existing component by extending their capabilities. In an HOC, features are added to a stable component by wrapping it inside a functional component that returns the enhanced version.

Forwarding refs using React.forwardRef() inside a higher order component is a common pattern of adding features to ref controlled nodes.

Below, we extend our <Input /> component by to display error messages using a withErrorMessage() HOC:

Show `withErrorMessage` HOC and related updates to `
`
import React, { useRef } from "react";

export function Form() {
const inputRef = useRef(null);
const handleClick = () => inputRef.current.focus();

return (
<div>
// highlight-next-line c{" "}
<button className="button" onClick={handleClick}>
Focus Input Field
</button>
</div>
);
}

export const Input = React.forwardRef((props, inputRef) => {
const { label, ...otherProps } = props;

return (
<div>
<div className="form-control">
<label className="form-label">{label}</label>
<input className="form-field" {...otherProps} ref={inputRef} />
</div>
</div>
);
});

const withErrorMessage = (Component) => {
const EnhancedComponent = ({ error, ...props }, ref) => {
const hasError = !!error?.message;

return (
<>
<Component {...props} ref={ref} />
{hasError && <div className="error-message">{error?.message}</div>}
</>
);
};
return React.forwardRef(EnhancedComponent);
};

export const InputWithErrorMessage = withErrorMessage(Input);

Let's break down the withErrorMessage component HOC first:

const withErrorMessage = (Component) => {
const EnhancedComponent = ({ error, ...props }, ref) => {
const hasError = !!error?.message;

return (
<>
<Component {...props} ref={ref} />
{hasError && <div className="error-message">{error?.message}</div>}
</>
);
};
return React.forwardRef(EnhancedComponent);
};

export const InputWithErrorMessage = withErrorMessage(Input);

The withErrorMessage() higher-order component is designed with the <Input /> component in mind. It accepts a ref forwarded component as an argument and frames a new EnhanacedComponent that is returned with its parent's refs forwarded: React.forwardRef(EnhancedComponent).

The exposed version of EnhancedComponent is stored in InputWithErrorMessage. It can be passed a ref from its parent and forwarded to EnhancedComponent along with its props and additional props (error here). It then adds desired features (the error message div) on top of the <Component /> itself.

We can now render the <InputWithErrorMessage /> component with all props passed to the original <Input /> component, including the ref prop:

<InputWithErrorMessage
label="Title"
error={{ message: "You got an error" }}
ref={inputRef}
/>

In addition, we can also pass new props that affect the added features: i.e. the error object to display a message. Passing and displaying an error message like this can help <Input /> component integrate with external form management libraries such as React Hook Form to display field errors.

Notice, that we did not need to make any change to the original <Input /> component itself. If its content is stabilized in our codebase, we can leave it as it is. So, the composite HOC derived with React.forwardRef() contributes to code stability, reusability, maintainability, and growth.

React.forwardRef() for Reusable Components

Composing robust reusable components with React.forwardRef() helps integrate with third-party libraries. For example, React Hook Form relies on refs extensively to internally set ref attributes to form fields by default.

Versatile design systems such as Shadcn/ui build on top of React Hook Form refs and make use of ref forwarding with React.forwardRef() to help produce collections of superior UI components.

For example, the internal implementation of a Shadcn/ui <Input /> component would look like this:

Show Shadcn/ui Input component code
import * as React from "react";

import { cn } from "@/lib/utils";

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = "Input";

export { Input };

Other Shadcn/ui components such as <Form /> and related subcomponents also use React.forwardRef() to forward refs from a parent to a target DOM node. You can check their implementations from the <Form /> docs here.

React.forwardRef: Benefits & Best Practices

In this section, we discuss the benefits, use cases, and some best practices centered around ref forwarding in React.

Benefits of using React forwardRef()

The most obvious benefit of React forwardRef() is that it enables the use of refs in child functional components by allowing a ref prop. It also makes the value of the ref available to the function's internals. That way, React forwardRef() helps associate descendent DOM nodes to a parent component.

Useful consequences of this behavior are:

  • Child DOM nodes can be targeted with a forwarded ref value.
  • Offers a meaningful React way of targeting DOM nodes, by replacing native JavaScript APIs such as document.getElementById().
  • Parent component can manipulate DOM elements rendered inside a functional component.
  • refs forwarding can be combined with hooks, higher-order functions (HOCs), and render props patterns to compose robust and reusable components.

React.forwardRef(): Use Cases

React forwardRef() mainly serves the purpose of exposing a DOM node with a ref to a parent. Common use cases involve triggering event-based actions such as:

  • Focusing a node on an event.
  • Scrolling to a node.
  • Selecting a text.
  • Triggering an animation.
  • Triggering custom-defined actions, such as form field validations in React Hook Form-based forms.

React.forwardRef(): Some Best Practices

React ref forwarding is associated with initializing refs with createRef() and useRef(). So, we need to follow the best practices related to them as well as those around forwardRef().

With regards to ref usage:

  • We shouldn't overuse refs.
  • We should use refs for imperative behaviors such as DOM event handling, animations, and so on.
  • Never use refs to replace states for updates that trigger a re-render.
  • Use refs to store mutable values that don't need the JSX to re-render.
  • Use refs as escape hatches that connect to external libraries and browser APIs such as DOM nodes.

While using forwardRef() we need to be aware of the pitfalls involved in forwarding ref values and setting the ref attribute:

  • Make sure the ref gets properly initialized in the parent. Use createRef() in class components, and useRef() in functional components.
  • Make sure to pass the ref prop to the enhanced child functional component returned from React.forwardRef(). Otherwise, there is no question of a ref being forwarded.
  • Use the React.forwardRef() method with the ref value accepted as the second argument, not as part of the props object. Use it inside the component.
  • Set the target DOM element's ref attribute to the forwarded value properly. Edge cases must be managed if they should be set conditionally. Otherwise, the target could be set to null.

Summary

In this post, we discussed in depth how ref forwarding with React.forwardRef() works in React. We touch on the underlying concepts of the ref attribute and its values on a JSX node, how it is used to target a DOM node, and how functional components need to be introduced in a ref prop.

We delved into ref forwarding with a <VideoPlayer /> example and investigated how React.forwardRef() makes it easy to implement custom controls on a <video> element by manipulating its DOM APIs from inside a parent component. Later on, with an <InputWithErrorMessage> component example, we examined how to write composite higher-order components with refs forwarded with React.forwardRef().

In the later part of the post, we explained how React.forwardRef() helps control DOM APIs of JSX elements needed by third-party libraries like React Hook Form and Shadcn/ui. Finally, we discussed the benefits and use cases of React.forwardRef() as well as some best practices to be maintained while implementing ref forwarding in React.