forwardRef

Lets a component expose a DOM node to its parent by forwarding the ref it receives.

Signature
const SomeComponent = forwardRef(render)
  • Added in React 16.3.0
  • Deprecated since PHP 19.0.0

Parameters

ParameterTypeDescription
$render(props, ref) => ReactNodeThe 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

Try it yourself
Loading editor…
Loading React…

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.

Example · jsx
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.

Example · jsx
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

  • Be the first to comment on this lesson.