Quiz

What is `forwardRef()` in React used for?

Topics
React

TL;DR

As of React 19 (December 2024), forwardRef() is deprecated. Function components can now accept ref as a regular prop, so wrapping in forwardRef() is no longer required. forwardRef() historically existed because, before React 19, function components could not receive a ref prop and forwardRef() was the official workaround for forwarding a parent's ref down to a child DOM node or component.

// Modern (React 19+): ref is a regular prop
function MyInput({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// Legacy (React 18 and earlier): wrap with forwardRef
import { forwardRef } from 'react';
const MyInputLegacy = forwardRef((props, ref) => (
<input ref={ref} {...props} />
));

What is forwardRef() in React used for?

The modern answer (React 19+)

In React 19, ref is just a regular prop on function components. You can destructure it like any other prop and pass it through to a DOM element or child component. There is no longer any reason to reach for forwardRef() in new code:

import { useRef } from 'react';
function MyInput({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
function ParentComponent() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<div>
<MyInput ref={inputRef} placeholder="Type here..." />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}

The React team ships a codemod that automatically converts existing forwardRef() usages to the new prop form. forwardRef() itself still works for now but logs a deprecation warning and is expected to be removed in a future major.

Why forwardRef() existed (legacy context)

Before React 19, ref was a "magic" prop that React intercepted — passing ref to a function component did nothing useful. forwardRef() was the API React provided to opt a function component into receiving a ref alongside its props, so the parent could reach a specific DOM node inside it (e.g. focus an input, measure a node, integrate with imperative third-party libraries).

import { forwardRef, useRef } from 'react';
// Legacy pattern — still works in React 19, but deprecated
const MyInput = forwardRef((props, ref) => <input ref={ref} {...props} />);
function ParentComponent() {
const inputRef = useRef(null);
return <MyInput ref={inputRef} placeholder="Type here..." />;
}

useImperativeHandle

When you want to expose a custom imperative API to the parent (rather than the raw DOM node), pair ref with useImperativeHandle. This is unchanged in React 19 — only the ref-forwarding mechanism has changed.

import { useImperativeHandle, useRef } from 'react';
function FancyInput({ ref }) {
const inputRef = useRef(null);
useImperativeHandle(
ref,
() => ({
focus: () => inputRef.current?.focus(),
clear: () => {
if (inputRef.current) inputRef.current.value = '';
},
}),
[],
);
return <input ref={inputRef} />;
}

The parent now sees { focus, clear } on the ref instead of the underlying input element. Use this sparingly — it is an escape hatch out of the declarative model.

Things to keep in mind

  • Class components: A ref attached to a class component receives the class instance directly. They have never needed forwardRef(), and that is unchanged.
  • Forward to a DOM node or imperative handle: A ref must ultimately be attached to a DOM element, a class instance, or an object returned from useImperativeHandle. Forwarding it to another function component just makes that component the new owner of the ref prop.
  • Migration: If you maintain a library that ships forwardRef-wrapped components, dropping the wrapper requires a peer-dependency bump to React 19. Many libraries currently ship both forms during the transition.

Further reading

Edit on GitHub