forwardRef
Lets a component expose a DOM node to its parent by forwarding the ref it receives.
const SomeComponent = forwardRef(render)- Added in React 16.3.0
- Deprecated since PHP 19.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$render | (props, ref) => ReactNode | The render function for your component. React calls it with the props and the ref your component received from its parent. The JSX you return is the output of your component. `props` is everything the parent passed except ref; `ref` is the ref attribute passed by the parent — an object or a function, or null if the parent passed none. Pass it on to another component or to useImperativeHandle. |
Return value
React componentA React component you can render in JSX. Unlike components defined as plain functions (before React 19), a component returned by forwardRef is also able to receive a ref prop.
Changelog
- PHP 19.0.0`ref` is available as an ordinary prop on function components, so `forwardRef` is no longer necessary. The docs mark it deprecated: it still works, and React says it "will be deprecated in a future release".
- PHP 18.0.0In Strict Mode the render function is invoked twice in development to surface accidental impurities; one result is discarded.
- PHP 16.3.0Introduced alongside `createRef` as the opt-in way for a component to forward a ref to a child.
Refs are not ordinary props on React 18 and earlier: writing <MyInput ref={inputRef} /> gives you the component instance, or nothing at all for a function component. forwardRef opts a component in, handing the ref to your render function as a second argument so you can attach it to whatever node you choose.
React 19 changes the recommendation
From React 19 ref is a normal prop on function components: function MyInput({ ref, ...rest }) works with no wrapper, and this is the form the docs now recommend. forwardRef still works and is still what you write for libraries that support React 18. React says it will be deprecated in a future release, so treat new forwardRef code as legacy rather than broken.
When to reach for it
Use it for leaf-level design-system components that wrap a DOM element — inputs, buttons, scroll containers — so callers can focus, measure or scroll them. Do not forward refs by default from feature components: a ref is an escape hatch, and exposing every internal node makes refactoring the internals a breaking change. When you need to expose behaviour rather than a node, combine it with useImperativeHandle.
Example
When to use it
- Let a form library call .focus() on a design-system <TextField> without knowing its internals.
- Measure a wrapped scroll container's height from its parent with getBoundingClientRect.
- Expose a narrow imperative API — open(), scrollToTop() — from a component via useImperativeHandle.
Frequently asked questions
Is forwardRef removed in React 19?
No, it still works in React 19 — the docs mark the page deprecated because ref is now a plain prop on function components, and say forwardRef will be deprecated in a future release. Existing code keeps working, and a codemod is available to convert components to the ref-as-a-prop form.
Why is my ref to a custom component always null?
Either the component never attaches the ref to a DOM node, or it is a React 18 function component that was not wrapped in forwardRef. Trace the ref from the parent down to a host element such as <input> or <div>; if it stops at another custom component that ignores it, the chain is broken there.
How do I use forwardRef with memo?
Wrap in either order — memo(forwardRef(render)) or forwardRef inside memo — both are supported, and memo accepts forwardRef components explicitly. On React 19 you can drop forwardRef entirely and memoize a plain function component that reads ref from its props.
More examples
Exposing a narrow API with useImperativeHandle
searchRef.current is the object returned by useImperativeHandle, not the <input> element, so the parent can call focus() and clear() but cannot reach in and set styles or read internal DOM. This keeps the component's internals refactorable: swapping the input for a contenteditable div only requires changing the two methods.
import { forwardRef, useImperativeHandle, useRef } from "react";
const SearchField = forwardRef(function SearchField(props, ref) {
const inputRef = useRef(null);
// The parent gets exactly these two methods — not the raw DOM node.
useImperativeHandle(
ref,
() => ({
focus() {
inputRef.current.focus();
},
clear() {
inputRef.current.value = "";
}
}),
[]
);
return <input ref={inputRef} placeholder="Search" {...props} />;
});
export default function App() {
const searchRef = useRef(null);
return (
<div>
<SearchField ref={searchRef} />
<button onClick={() => searchRef.current.focus()}>Focus</button>{" "}
<button onClick={() => searchRef.current.clear()}>Clear</button>
</div>
);
}Forwarding through a wrapper, and the null-ref trap
Both components accept a ref, but only FixedField attaches it. The console logs "broken: null" and "fixed: <input …>". React issues no warning for the broken case, which is why an always-null ref is the single most common forwardRef bug: the wrapper opted in but forgot to hand the ref to a host element.
import { forwardRef, useEffect, useRef } from "react";
// Broken: the ref arrives but is never attached to a host element.
const BrokenField = forwardRef(function BrokenField(props, ref) {
return (
<div>
<input {...props} />
</div>
);
});
// Fixed: the ref is passed all the way down to the DOM node.
const FixedField = forwardRef(function FixedField(props, ref) {
return (
<div>
<input ref={ref} {...props} />
</div>
);
});
export default function App() {
const brokenRef = useRef(null);
const fixedRef = useRef(null);
useEffect(() => {
console.log("broken:", brokenRef.current); // null
console.log("fixed:", fixedRef.current); // <input>
}, []);
return (
<div>
<BrokenField ref={brokenRef} defaultValue="broken" />
<FixedField ref={fixedRef} defaultValue="fixed" />
<p>Check the console for the two ref values.</p>
</div>
);
}
Discussion