Skip to content
React rc advanced 4 min read

Forwarding Refs

Refs let you reach a DOM node imperatively — to focus an input, scroll an element into view, or measure its size. But when you attach a ref to your own component instead of a built-in element, it does not “just work”: React intercepts the ref and there is no DOM node to give back. Forwarding refs is the mechanism that lets a parent component reach through your component and grab the inner DOM node it actually cares about. This page covers why refs do not pass through by default, how to forward them, how to expose a custom imperative handle, and how the React 19 ref-as-prop change simplifies everything.

Why refs do not pass through by default

ref is a reserved prop, like key. When you render <MyInput ref={inputRef} />, React does not place inputRef inside MyInput’s props object. Instead it tries to attach the ref to the component instance — and function components have no instance. The result is that inputRef.current stays null.

function MyInput(props) {
  // props.ref is undefined here in React 18 — ref was consumed by React.
  return <input className="field" {...props} />;
}

function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus(); // 💥 inputRef.current is null
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>Focus</button>
    </>
  );
}

The parent wants to focus the underlying <input>, but the ref never reaches it.

Forwarding to an inner input

The classic solution (React 16.3–18) is forwardRef. It wraps your component so React passes the incoming ref as a second argument, which you then attach to the real DOM node.

import { forwardRef, useRef } from "react";

const MyInput = forwardRef(function MyInput(props, ref) {
  return <input ref={ref} className="field" {...props} />;
});

function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus(); // ✅ now points to the <input>
  }

  return (
    <>
      <MyInput ref={inputRef} placeholder="Email" />
      <button onClick={handleClick}>Focus</button>
    </>
  );
}

Now inputRef.current is the actual <input> element, and focus() works. The component author decides which inner node the ref binds to, keeping the rest of the DOM structure private.

Forwarding gives the parent access to a specific node you choose to expose — not your whole component. Treat the forwarded ref as a deliberate part of your public API.

Combining with useImperativeHandle

Sometimes you do not want to hand over the raw DOM node. Maybe you want to expose only focus() and a custom scrollIntoView(), while hiding everything else. useImperativeHandle lets you define exactly what ref.current becomes.

import { forwardRef, useImperativeHandle, useRef } from "react";

const SearchField = forwardRef(function SearchField(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => ({
    focus() {
      inputRef.current.focus();
    },
    clear() {
      inputRef.current.value = "";
      inputRef.current.focus();
    },
  }), []);

  return <input ref={inputRef} type="search" {...props} />;
});

function Toolbar() {
  const fieldRef = useRef(null);

  return (
    <>
      <SearchField fieldRef={undefined} placeholder="Search…" ref={fieldRef} />
      <button onClick={() => fieldRef.current.focus()}>Focus</button>
      <button onClick={() => fieldRef.current.clear()}>Clear</button>
    </>
  );
}

The parent can call fieldRef.current.focus() and fieldRef.current.clear(), but it cannot reach the underlying <input> directly. This is the recommended way to expose a constrained imperative API.

The React 19 ref-as-prop change

React 19 removes the need for forwardRef in most cases. ref is now a regular prop that function components receive like any other. You destructure it directly — no wrapper required.

import { useRef } from "react";

// React 19: ref arrives as a normal prop.
function MyInput({ ref, ...props }) {
  return <input ref={ref} className="field" {...props} />;
}

function Form() {
  const inputRef = useRef(null);
  return (
    <>
      <MyInput ref={inputRef} placeholder="Email" />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </>
  );
}

useImperativeHandle still works the same way — you just pass the ref prop into it instead of a forwarded argument.

function SearchField({ ref, ...props }) {
  const inputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus(),
  }), []);
  return <input ref={inputRef} type="search" {...props} />;
}

forwardRef is not removed in React 19 — existing code keeps working — but it is deprecated and will be cleaned up in a future major version. A codemod is provided to migrate automatically.

TypeScript signatures

import { useRef } from "react";

interface MyInputProps extends React.ComponentPropsWithoutRef<"input"> {
  ref?: React.Ref<HTMLInputElement>;
}

function MyInput({ ref, ...props }: MyInputProps) {
  return <input ref={ref} {...props} />;
}

API comparison

ApproachReact versionHow ref arrivesBest for
Not forwardedanynot availablecomponents that never expose a node
forwardRef16.3+second function argumentlibraries supporting React 18 and below
ref-as-prop19+normal propnew code on React 19
useImperativeHandle16.8+wraps either of the aboveexposing a custom, limited API

Output:

forwardRef    → (props, ref) => <input ref={ref} />
React 19      → ({ ref, ...props }) => <input ref={ref} />

Best practices

  • Forward refs from reusable, leaf-like components (inputs, buttons, custom controls) so consumers can integrate with focus management and form libraries.
  • Forward to exactly one meaningful DOM node; if there are several, prefer useImperativeHandle to expose a clear, named API.
  • Use useImperativeHandle to hide implementation details — return only the methods callers actually need.
  • On React 19, write new components with ref as a plain prop and skip forwardRef entirely.
  • When publishing a library that must support React 18, keep forwardRef for backward compatibility.
  • Avoid using refs to do what props and state can do; reach for forwarding only when you genuinely need imperative DOM access.
  • Always null-check ref.current before calling methods, since it is null before mount and after unmount.
Last updated June 14, 2026
Was this helpful?