Skip to content

ref as a regular prop: goodbye, forwardRef

In React 19, function components receive ref directly in props. And ref callbacks can finally return a cleanup function.

Author
Jan Jarčík
Published
Reading time
2 min read
Updated
ShareXLinkedInBluesky

forwardRef was a necessary evil for years: if you wanted your <Input> component to accept a ref, you had to wrap it and live with uglier types. React 19 removes the need — ref is a regular prop for function components.

Before and after

TSX
// React 18
const Input = forwardRef<HTMLInputElement, InputProps>(function Input(props, ref) {
  return <input ref={ref} {...props} />;
});
TSX
// React 19
function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} {...props} />;
}

The component is a plain function, DevTools show the right name and the types are readable. If you type props with React.ComponentProps<"input">, ref is already included.

forwardRef still works, but it will be deprecated in a future version. There's a codemod for the migration.

Cleanup in ref callbacks

Ref callbacks used to be called with null on unmount. Now you can return a cleanup function — exactly like from an effect:

TSX
<div
  ref={(node) => {
    const observer = new ResizeObserver(([entry]) => {
      setWidth(entry.contentRect.width);
    });
    observer.observe(node);
    return () => observer.disconnect();
  }}
/>

When you return a cleanup, React won't call the callback with null on unmount. TypeScript gotcha: implicit returns (ref={(n) => (instance = n)}) no longer type-check — use curly braces.

When to reach for useImperativeHandle

If you don't want to expose the whole DOM node but only a few methods (focus, scrollToBottom), combine the ref prop with useImperativeHandle(ref, () => ({ … })). It works just like before — minus the wrapping.