Trust is earned, not given

A different perspective

2025-07-15 · Projects

React.js, part 14: ref as a prop, ref cleanup, and the refs you should stop writing

Part 14from the React.js series · 20 parts in all

For years passing a ref through a component meant wrapping it in forwardRef — a ceremony with no equivalent for any other prop. React 19 removed it: ref is now an ordinary prop. Combined with cleanup functions returned from ref callbacks, this closed a quiet memory-leak source. Part 14 is the short, satisfying cleanup.

ref is a prop now

// Before: a whole wrapper just to punch a hole through the component.
const Field = forwardRef(function Field(props, ref) {
  return <input ref={ref} {...props} />;
});

// Now: ref arrives like any other prop.
function Field({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

// Usage is unchanged.
<Field ref={inputRef} placeholder="Email" />

forwardRef still works and is not going anywhere soon, but new code does not need it, and a component can now spread ref the way it spreads the rest of its props — which fixes a long-standing inconsistency with component libraries.

Ref callbacks can return a cleanup

Historically a ref callback that set up an observer returned nothing, and the only teardown point was calling the callback again with null. React 19 supports the same shape effects use — return a function:

function Tooltip({ text }) {
  const cleanupRef = (node) => {
    if (!node) return;                    // React may still call with null
    node.classList.add('has-tooltip');
    node.setAttribute('title', text);

    // Returned cleanup runs when the node detaches OR the ref changes.
    return () => {
      node.classList.remove('has-tooltip');
      node.removeAttribute('title');
    };
  };
  return <span ref={cleanupRef}>{text}</span>;
}

That symmetry closes the gap where a ref callback attached a listener or an IntersectionObserver and the teardown was a separate, easily forgotten null branch.

The refs to stop writing

Next: document metadata and stylesheets, which React 19 now hoists for you instead of requiring a helmet library.