skip to content

In React 19, how does a parent attach a ref to the <input> inside your custom function component, and what did forwardRef have to do before React 19?

level: middleimportance: should knowfreq 55%

answer

  1. reserved attribute, then ordinary prop
  2. the child chooses where it lands
  3. two-argument calling convention, historically
  4. wrapper deleted, prop destructured
  5. classes still hand back the instance

basics

~20 s

In React 19, ref is an ordinary prop: accept it alongside the other props and pass it to the inner <input>. Before React 19, function components dropped ref, so you had to wrap them in forwardRef((props, ref) => ...).

solid answer

~50 s

Since React 19, `ref` is a normal prop on function components. You write `function TextField({ label, ref, ...rest })` and pass `ref` straight to the element you want the parent to reach, so `<TextField ref={inputRef} />` lands on the inner `<input>`. Before React 19, `ref` was stripped out and never reached your props — passing one to a plain function component warned and did nothing — so the component had to be wrapped in `forwardRef`, which received it as a second argument after props. `forwardRef` still works in React 19 and the release notes say it will be deprecated in a future version, so new code should take `ref` as a prop. Two details survive unchanged: for class components `ref` still means the instance and is still not a prop, and `key` is still not a prop either.

code

javascript · 19 lines
javascript
import { useRef } from 'react';

function TextField({ label, ref, ...rest }) {
  return (
    <label>
      {label}
      <input ref={ref} {...rest} />
    </label>
  );
}

export function LoginForm() {
  const emailRef = useRef(null);
  return (
    <form onSubmit={() => emailRef.current.focus()}>
      <TextField label="Email" ref={emailRef} name="email" />
    </form>
  );
}

go deeper

for a junior

Remember that in React 19 you write ref in your props list and hand it to the element the parent should reach. Recognise forwardRef in older files as the wrapper that used to be needed.

for a middle

Explain why a ref to a custom component is ambiguous at all — the child owns the decision of which node it points at — and show the mechanical migration from the two-argument forwardRef signature to a plain prop.

for a senior

Talk about the contract: forwarding to the root node exposes the full DOM element to callers and locks your markup in place. Be ready to say when you would forward, when you would narrow the handle, and how you would migrate a component library incrementally.

for a principal

Own the library-wide policy: which components expose refs at all, what they promise, and how you keep an escape hatch from becoming the default integration path. Weigh a codemod-style sweep against opportunistic migration for a shared design system.

## What a ref prop is actually asking for When a parent writes `<input ref={inputRef} />`, React attaches the real DOM node to `inputRef.current`. When the parent writes `<TextField ref={inputRef} />`, there is no single obvious node — `TextField` may render a wrapper, a label, an input and a hint. So the decision of *where the ref lands* belongs to the child, and the whole question is how the child receives the request. ## Before React 19: forwardRef Historically `ref` was treated like `key`: a reserved attribute React consumed itself rather than passing through as a prop. A function component therefore could not see it. Passing a ref to one produced a development warning and `current` stayed `null`. The fix was `forwardRef`, a wrapper that changes the component's calling convention to take two arguments: ```jsx const TextField = forwardRef(function TextField(props, ref) { return <input ref={ref} {...props} />; }); ``` That worked, but it cost a wrapper around every component in a design system, awkward generic typing in TypeScript, and a second parameter that new readers had to be told about. ## React 19: ref is a prop React 19 makes `ref` an ordinary prop for function components. Declare it and use it: ```jsx function TextField({ label, ref, ...rest }) { return ( <label>{label}<input ref={ref} {...rest} /></label> ); } ``` Because it is now a plain prop, everything you already know about props applies. It can be renamed on the way down, spread with `{...rest}` onto a child element, handed to a different element than the parent expected, forwarded through two levels without two wrappers, or passed to more than one place. It is also just another entry in your props type, so `React.ComponentProps` and ordinary generics work without special helpers. `forwardRef` has not been removed — existing code keeps working, and the React 19 release notes state it will be deprecated in a future version. Treat it as legacy you can read and migrate, not as the way to write a new component. ## What did not change - **Class components.** A ref on a class component still resolves to the component *instance*, and it is still not delivered as a prop. `forwardRef` never applied to classes either. - **`key`.** Still reserved, still not a prop, still invisible to the component. - **Host elements.** A ref on `<div>`, `<input>` or any DOM tag still receives the DOM node; nothing about that changed. ## The design consequence Because the child decides where a forwarded ref lands, exposing a ref is an API decision, not a mechanical pass-through. A component that forwards to its root `<input>` promises callers the full `HTMLInputElement` surface — every method and mutable property on it. That promise is often too wide: it lets a caller reach in and set `value`, change classes, or read layout, all outside React's data flow, and it silently breaks when you later swap the root element for a wrapper. When you want the parent to reach in at all but not that far, the narrowing tool is `useImperativeHandle`, which replaces what `ref.current` points at with an object you define. ## Migration in practice Unwrapping is mechanical: delete the `forwardRef(...)` call, move `ref` into the destructured props, and drop the second parameter. Keep the ref name identical so call sites do not change. If the component was typed as `ForwardRefExoticComponent`, the type usually collapses to a plain function-component signature with `ref` in its props, which is one of the quieter wins of the change.

  • Does making ref a prop change anything for class components?
    No. A ref on a class component still resolves to the component instance and is still not delivered as a prop, exactly as before. `forwardRef` never covered classes either. The React 19 change is specifically about function components receiving `ref` through the normal props channel.
  • What is the risk of routinely forwarding a ref to your component's root DOM node?
    You are promising callers the whole DOM element surface — every method and mutable property — outside React's data flow. That is a wide contract to keep, and it breaks the day you wrap the root in another element. Expose a deliberate handle instead when the caller needs only one or two operations.
  • You inherit a component wrapped in forwardRef. Should you rewrite it?
    It still works in React 19, so nothing is urgent, but the release notes say `forwardRef` will be deprecated in a future version. The rewrite is mechanical — remove the wrapper, destructure `ref` from props, drop the second parameter — and call sites stay untouched, so it is cheap to do when you are already in the file.

saying these in an interview costs you the question

  • Says forwardRef is still required in React 19
  • Thinks ref on a class component arrives as a prop
  • Claims key is now a prop as well
  • Assumes a ref prop always lands on the root element
  • Believes forwardRef was removed in React 19

context