[DIP] ref as a prop

Tech

While reading through the React v19 Stable documentation, I noticed that a ref can be passed as a component prop, and I became curious why it wasn't passed in components on earlier versions. Also, seeing that forwardRef is no longer used in later versions and is scheduled to be removed, I became curious about why forwardRef was created and why it is being removed, so I started investigating.

ref-as-a-prop-1



The introduction of forwardRef

As function components became the primary usage in React, and since function components do not have their own instance, forwardRef was introduced to make ref usage possible.


Unlike class components, function components operate as simple function calls, so they do not have an instance. This can be confirmed with a simple component implementation. When handled in a TypeScript file, passing a ref to a function component causes a type error so it cannot be passed, and in JavaScript it can be passed but produces a Warning.

jsx
class ClassComponent extends Component { render() { return <div>class</div>; } } const FuctionComponent = () => { return <div>function</div>; }; export default function App() { const classRef = useRef(); const funcRef = useRef(); useEffect(() => { console.log("class", classRef.current); // ClassComponent instance }, []); useEffect(() => { console.log("func", funcRef.current); // undefined }, []); return ( <div className="App"> <ClassComponent ref={classRef} /> <FuctionComponent ref={funcRef} /> </div> ); }


forwardRef internally wraps the function component and changes it so that the function component can receive a ref along with its props. The change simply converts the passed function component into an object with the type “REACT_FORWARD_REF_TYPE”, and the ref references that object.

tsx
const REACT_FORWARD_REF_TYPE: symbol = Symbol.for('react.forward_ref'); export function forwardRef<Props, ElementType: React$ElementType>( render: ( props: Props, ref: React$RefSetter<React$ElementRef<ElementType>>, ) => React$Node, ) { ... const elementType = { $$typeof: REACT_FORWARD_REF_TYPE, render, }; return elementType; };


forwardRef render

In the process by which forwardRef is rendered, the object holding the function component goes through the Render Phase, where createFiberFromTypeAndProps creates a new Fiber node based on the type and props.

jsx
export function createFiberFromTypeAndProps( type: any, // React$ElementType key: null | string, pendingProps: any, owner: null | ReactComponentInfo | Fiber, mode: TypeOfMode, lanes: Lanes, ): Fiber { ... switch (type.$$typeof) { ... case REACT_FORWARD_REF_TYPE: fiberTag = ForwardRef; break getTag; ... } ... const fiber = createFiber(fiberTag, pendingProps, key, mode); fiber.elementType = type; fiber.type = resolvedType; fiber.lanes = lanes; return fiber; };

ref-as-a-prop-3



React v19 ref as a prop

jsx
function ReactElement( type, key, self, source, owner, props, debugStack, debugTask, ) { ... const refProp = props.ref; ... element = { $$typeof: REACT_ELEMENT_TYPE, type, key, ref, props, }; ... return element; }

Here, in React v19, the ref passed to a function component connects the ref that was passed as a prop to the element object, but since it is simply passed as a prop and does not reference the function component object itself, you must manually assign the reference.

In fact, even if you run the code used above on React v19, no warnings or errors occur, but you can see that funcRef is printed as undefined.


Manually assigning a reference for a ref in a function component must be done either by using the useImperativeHandle hook or by assigning it directly to a component written in JSX.

jsx
import { useRef, useImperativeHandle } from "react"; function MyInput({ ref }) { const inputRef = useRef(null); useImperativeHandle( ref, () => { return { focus() { inputRef.current.focus(); }, scrollIntoView() { inputRef.current.scrollIntoView(); }, }; }, [] ); return <input ref={inputRef} />; }


forwardRef deprecate and remove

As passing a ref to a function component became possible in React v19, forwardRef is no longer needed, and it is reportedly scheduled to be deprecated in future versions.

With forwardRef no longer being used, the additional, unnecessary (?) boilerplate-like code of wrapping a function component with forwardRef just to pass a ref will be reduced, which is expected to make code easier to read.

If you are migrating to React v19, doing the work of removing forwardRef ahead of time seems like it would let the project run without issues even when migrating to a future version where forwardRef is no longer supported.



In conclusion

ref-as-a-prop-2

A personal thought I had while looking into this: any frontend developer who has used React has probably thought at some point that the developers who build React are likely extremely skilled, brilliant developers. (Maybe that's just me…) Even these brilliant developers built React with great ideas and deep consideration, yet later, following better usability or direction, they discard and revise code they had written before. Watching this process, it feels like we too create the best, most optimal code for a given situation, and later look back at our old code, feel embarrassed, and revise it in a similar way.

Looking at old code and thinking, 'why did I write it like that back then?', feeling embarrassed and yet revising it — I think that very process itself may be evidence of our growth.