ESC

Type to search the knowledge base.

Forwarding Refs

forwardRef and ref props let parents access a child’s DOM node: input focus, measuring, and the ref-as-prop modern pattern.

intermediate4 min read
  • react
  • forwarding-refs

Parents sometimes need a handle to a child’s DOM node: focus an input, measure width, scroll into view, or integrate a non-React library. Refs do that without forcing the child to expose a bulky imperative API.

In current React, function components can accept ref as a normal prop (React 19). Older codebases use forwardRef. Both exist in the wild — know both.

Docs: Referencing Values with Refs, forwardRef.

React 19 style: ref as prop

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

function Form() {
  const inputRef = useRef(null);
  return (
    <>
      <TextField label="Email" ref={inputRef} type="email" />
      <button type="button" onClick={() => inputRef.current?.focus()}>
        Focus email
      </button>
    </>
  );
}

Classic forwardRef

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

Without forwarding, ref on a custom component does not attach to an inner DOM node (and in older React, was not a regular prop).

Callback refs and lists

const map = useRef(new Map());

items.map((item) => (
  <li
    key={item.id}
    ref={(node) => {
      if (node) map.current.set(item.id, node);
      else map.current.delete(item.id);
    }}
  >
    {item.label}
  </li>
));

Do not overuse

Prefer declarative props when possible (autoFocus, controlled value, CSS). Refs are escape hatches for:

  • focus management
  • measuring layout (useLayoutEffect)
  • third-party widgets
  • media playback imperative APIs

Exposing a whole grab-bag of imperative methods belongs in useImperativeHandle with a narrow surface.

Interview out-loud

“Refs hold mutable values that do not trigger re-render. To let a parent reach a child’s DOM node I forward the ref to an inner input or div — via ref-as-prop in React 19 or forwardRef earlier. I keep imperative surfaces small and prefer props for ordinary data flow.”

Further reading

Production checklist

  1. Source of truth clear for every piece of UI state?
  2. Remount, route change, and Strict Mode cleanup paths handled?
  3. Urgent updates separated from deferrable work?
  4. Profiled before memo, virtualization, or context splits?
  5. Keyboard, focus, and accessible names still work after the change?

Edge cases worth rehearsing

Interviewers and production incidents cluster around the same edges: first render versus update, empty and loading states, Strict Mode double setup, concurrent interruptions, and what happens when identity (key, route params, user id) changes mid-edit. Walk one concrete user journey end-to-end — open, edit, navigate away, come back — and say which state survives.

Prefer fixing data flow and ownership before reaching for memoization or micro-optimizations. Prefer event handlers over effects when a user action is the trigger. Prefer deriving values during render over mirroring props into state. Prefer stable list keys from business ids. Measure with the profiler when performance is the claim.

When you cite an API, mention one failure mode: abort on unmount, serializable props across server/client boundaries, focus restoration for dialogs, or cache invalidation after a mutation. Specific beats generic every time.

Edge cases worth rehearsing

Interviewers and production incidents cluster around the same edges: first render versus update, empty and loading states, Strict Mode double setup, concurrent interruptions, and what happens when identity (key, route params, user id) changes mid-edit. Walk one concrete user journey end-to-end — open, edit, navigate away, come back — and say which state survives.

Prefer fixing data flow and ownership before reaching for memoization or micro-optimizations. Prefer event handlers over effects when a user action is the trigger. Prefer deriving values during render over mirroring props into state. Prefer stable list keys from business ids. Measure with the profiler when performance is the claim.

When you cite an API, mention one failure mode: abort on unmount, serializable props across server/client boundaries, focus restoration for dialogs, or cache invalidation after a mutation. Specific beats generic every time.

Related guides