forwardRef lets a parent component pass a ref through your component to a DOM element inside it, so the parent can call focus(), scrollIntoView() and similar methods on that element. In React 19 you no longer need it: function components receive ref as an ordinary prop. forwardRef still works, and you will see it in most existing code, so this page shows both.
forwardRef takes a render function with two arguments: props, and the ref the parent passed. ref is not inside props; it arrives on its own. Click Focus and the console confirms that inputRef.current is the real <input> node.
The React 19 way: ref is a prop
Since React 19 a function component gets ref in its props like any other prop. No wrapper, no second argument:
Type something and click Log value: the parent reads the input's current text straight from the DOM node. Delete ref={ref} from the <input> and click Focus: the console shows an error, because inputRef.current is now null.
forwardRef (all versions) | ref prop (React 19+) | |
|---|---|---|
| How the component gets the ref | Second argument, (props, ref) | props.ref |
| Wrapper needed | Yes | No |
Works with useImperativeHandle | Yes | Yes |
| Status | Works, planned for deprecation | The way to write new code |
A ref the child ignores stays null
Passing ref to a component does nothing by itself. The component has to put it on an element. If it does not, ref.current stays null:
The console shows Forgetful ref: null and Careful ref: INPUT. This is the most common reason for "ref.current is null" on a custom component. Before React 19, Careful would have been null too unless it was wrapped in forwardRef, since the ref was never handed to the function.
useImperativeHandle: expose a smaller API
Giving the parent the whole DOM node means it can do anything to it: change styles, remove children, read values you meant to keep private. useImperativeHandle lets you decide what ref.current contains instead:
The last button logs ["focus", "clear"]: the parent gets those two methods and nothing else, not the input node. Add a select() method to the object and a button that calls it, and the parent gains exactly one more ability.
The child keeps its own ref, inputRef, for the real input, and the handle wraps it. The third argument is a dependency array, like the one for useEffect: the handle is rebuilt when those values change.
Reach for this sparingly. Most things a parent wants (open, close, show an error) are better as props, like isOpen or error. Imperative methods are for actions that have no natural prop: focusing, scrolling, playing a video, starting an animation.
Using the ref inside the child as well
Sometimes the child needs the same DOM node for its own work, for example to measure it or to focus it after an error, while the parent also holds a ref to it. One ref attribute can only take one value, so merge the two with a ref callback:
function AutoGrowTextarea({ ref, ...props }) {
const localRef = useRef(null);
function setRefs(node) {
localRef.current = node;
if (typeof ref === 'function') ref(node);
else if (ref) ref.current = node;
}
return <textarea ref={setRefs} {...props} />;
}
The parent's ref may be an object from useRef or a function, so handle both. When the parent only needs a few actions, useImperativeHandle above is the cleaner choice, because the child keeps the node to itself.
Passing a ref through several layers
A ref travels one component at a time. If Form renders Field, which renders TextInput, which renders the <input>, each of those components has to pass ref on to the next. In React 19 that is one more prop to forward (<TextInput ref={ref} />); with forwardRef, every layer had to be wrapped. Spreading props ({...props}) does not carry it in older versions, because before React 19 ref was never part of props.
Design system components are the usual place this matters: a Button, Input or Select that wraps a native element should forward its ref, so the app using it can focus it, measure it, or hand it to a library that positions popovers.
Migrating from forwardRef
The change is mechanical: remove the wrapper and read ref from props.
// Before
const Button = forwardRef(function Button({ variant, ...props }, ref) {
return <button ref={ref} className={variant} {...props} />;
});
// After (React 19)
function Button({ variant, ref, ...props }) {
return <button ref={ref} className={variant} {...props} />;
}
There is no hurry. forwardRef keeps working in React 19, and a library that must support React 18 has to keep it, because React 18 does not pass ref as a prop. Class components are not affected: a ref on a class component still points to the component instance.
TypeScript
With forwardRef, the type arguments come in the order ref type, then props:
import { forwardRef } from 'react';
type FancyInputProps = { label: string };
const FancyInput = forwardRef<HTMLInputElement, FancyInputProps>(
function FancyInput({ label }, ref) {
return <input ref={ref} aria-label={label} />;
}
);
In React 19, type ref like any other prop. ComponentProps<'input'> already includes it:
import { useImperativeHandle, useRef, type ComponentProps, type Ref } from 'react';
function FancyInput(props: ComponentProps<'input'>) {
return <input {...props} />;
}
type SearchHandle = { focus: () => void; clear: () => void };
function SearchBox({ ref }: { ref?: Ref<SearchHandle> }) {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => {
if (inputRef.current) inputRef.current.value = '';
},
}));
return <input ref={inputRef} />;
}
// In the parent
const searchRef = useRef<SearchHandle>(null);
The useRef page covers refs themselves: DOM access, values that persist without rendering, and ref callbacks. For typing components in general, see React with TypeScript.
Frequently Asked Questions
What does forwardRef do in React?
It wraps a function component so that a ref given by the parent reaches the component as a second argument, (props, ref). The component then puts that ref on a DOM node, so the parent can call methods like focus() on it.
Is forwardRef deprecated in React 19?
Not yet, and it still works. React 19 passes ref to function components as a regular prop, so new code does not need it, and the React team has said it plans to deprecate forwardRef in a future version.
Why is my ref null on a custom component?
The component received the ref but did not put it on any element. Attach it to a DOM node inside: <input ref={ref} />. Before React 19 the ref was not passed at all unless the component was wrapped in forwardRef.
What is useImperativeHandle used for?
It replaces what the parent sees in ref.current. Instead of the whole DOM node you return an object with just the methods you choose, such as focus and clear.
Do class components get ref as a prop in React 19?
No. A ref on a class component still points to the component instance. The change applies to function components only.