ref as a Prop & Cleanup
Pass refs like any other prop and run cleanup when a ref detaches — no more forwardRef
React 19 makes two long-awaited ref improvements: function components can accept ref as a normal prop (goodbye forwardRef), and ref callbacks can return a cleanup function. Both remove boilerplate you've probably written many times.
ref Is Just a Prop Now
Before React 19, forwarding a ref to a DOM node inside your component required wrapping it in forwardRef:
// React 18 and earlier
const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
return <input ref={ref} {...props} />
})
In React 19, ref is an ordinary prop. Just destructure it:
// React 19
function Input({ ref, ...props }: Props & { ref?: Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />
}
Less ceremony, cleaner types, and it composes like every other prop.
Existing forwardRef components keep functioning — nothing breaks. It's now
deprecated in favor of ref-as-prop, and a future major version will remove
it. Migrate at your leisure; new code should use the prop form.
Ref Cleanup Functions
A ref callback runs when React attaches the element (with the node) and again when it detaches (with null). Historically you handled teardown by checking for null. React 19 lets the callback return a cleanup function instead — mirroring how useEffect works:
<input
ref={(node) => {
// setup: node is the DOM element
const observer = new ResizeObserver(() => {
/* ... */
})
observer.observe(node)
// cleanup: runs when the element detaches
return () => observer.disconnect()
}}
/>
No more if (node === null) branches. Setup and teardown live together, and React calls the returned function at exactly the right time.
element mounts → ref callback runs (setup)
element unmounts → cleanup function runs (teardown)
If your ref callback returns a cleanup function, React will no longer call
it a second time with null. Mixing the two styles in one
callback can double-run teardown — pick the cleanup-return form and keep all
teardown inside it.
Putting It Together
function AutoFocusInput({ ref, ...props }: Props) {
return (
<input
{...props}
ref={(node) => {
node?.focus()
return () => {
// any teardown when this input leaves the DOM
}
}}
/>
)
}
ref flows in as a prop, and the inline ref callback handles focus plus cleanup — all without forwardRef or lifecycle gymnastics.