React 19: Using ref as a Regular Prop Without forwardRef
AI generated
{ }
React · React 19 · TypeScript
ref as a Prop in React 19: Goodbye forwardRef Boilerplate
Migrating existing forwardRef components and staying compatible in mixed codebases

React 19 removes one of the framework's oldest special cases: ref can now be received directly as a regular prop, no forwardRef wrapper required. This article walks through the new pattern, how to migrate existing forwardRef components, TypeScript typing, and how both patterns coexist safely in a mixed codebase.

13 min read React 19 refs forwardRef TypeScript Migration

1. What Changed for refs in React 19

Through React 18, ref held a special status among props: anyone building a function component and wanting to access an internal DOM element or an imperative handle from outside via ref had to wrap the component in forwardRef. A plain function call like function MyInput(props) { ... } could not receive a ref prop at all, React stripped it out of props internally before the function was even invoked.

React 19 lifts this restriction: ref is now passed straight through to the function component just like any other prop, with no forwardRef wrapper needed. This noticeably cuts boilerplate, simplifies TypeScript typing, and turns component definitions back into plain functions without a special case. For new components, forwardRef has therefore become unnecessary in most situations.

2. The New Pattern: Receiving ref as a Plain Prop

The change at the code level is unspectacular: ref is simply listed as another field in the props destructuring, exactly like any other value. Inside the component, the ref can then be forwarded to a DOM element the usual way, either via useEffect or directly in JSX, exactly as you would with any regular prop.

This simplified pattern pays off immediately, especially for small, reusable UI building blocks like an input field or a button, because the previous forwardRef wrapper along with its second callback layer disappears entirely. The component stays a single, flat function, which also makes debugging in the React DevTools tree clearer, since no extra ForwardRef layer shows up in the component tree anymore.


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

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

  const focusInput = () => {
    inputRef.current?.focus();
  };

  return (
    <>
      <TextInput label="Name" ref={inputRef} />
      <button onClick={focusInput}>Focus</button>
    </>
  );
}

3. Why forwardRef Was Needed in the First Place

The historical reason for forwardRef lies in React's reconciliation model: ref was never an ordinary data flow from parent to child component, but an imperative side channel through which React reports back a DOM reference or an instance directly. To prevent this side channel from accidentally being passed through the component as a regular prop, potentially altered or ignored along the way, React consistently stripped ref out of the props object.

forwardRef gave component authors an explicit second function signature, (props, ref) => ..., through which that side channel was deliberately made accessible again. This worked reliably, but meant every reusable base component had to apply this wrapper preemptively, even when it was not yet clear at creation time whether ref would ever be needed, which in practice led to a lot of defensive boilerplate.

4. Migrating Existing forwardRef Components

Migrating a single component follows three small steps: remove the forwardRef call, merge the second function signature (props, ref) into a flat props destructuring with ref as an additional field, and keep the component name as a plain function or const declaration. The return value and the JSX inside the component body stay unchanged, only the outer function shell changes.

For larger codebases with many base components, a gradual migration component by component is preferable to one large rewrite, since forwardRef continues to work in React 19 and both patterns can coexist without issue. A good starting point is heavily used but simple components like buttons or input fields, where the effort per migration amounts to just a few lines.


// Before: React 18 pattern
const Button = forwardRef(function Button(props, ref) {
  return <button ref={ref} className="btn" {...props} />;
});

// After: React 19 pattern
function Button({ ref, ...props }) {
  return <button ref={ref} className="btn" {...props} />;
}

5. Typing It in TypeScript

Under React 18, a typed forwardRef component had to pass the ref type and the props type as two separate generics to forwardRef, an order that many developers regularly mixed up. With ref as a plain prop, it is enough to declare ref directly in the props interface with the appropriate ref type, usually Ref or a union of RefObject and RefCallback.

This not only simplifies the signature, it also makes generic, reusable components easier to type, since there is no separate ForwardRefExoticComponent construct to manage around the actual component anymore. For components meant to support multiple element types, say polymorphic buttons, the ref type can additionally be tied to the element kind through a generic type parameter.


import { Ref } from "react";

interface TextInputProps {
  label: string;
  ref?: Ref<HTMLInputElement>;
  placeholder?: string;
}

function TextInput({ label, ref, placeholder }: TextInputProps) {
  return (
    <label>
      {label}
      <input ref={ref} placeholder={placeholder} />
    </label>
  );
}

6. Backward Compatibility in Mixed Codebases

React 19 marks forwardRef as deprecated but does not remove it; existing code using forwardRef keeps running unchanged. This matters especially for projects pulling in third-party UI libraries whose own components still use forwardRef internally, since those libraries will only gradually adopt the new React 19 pattern themselves, and updating them is not within the project's own control.

In practice this means a single codebase can contain both patterns side by side for a long time without any functional problems: a component built with forwardRef can be used without issue inside a component that already uses the new ref-as-prop pattern, and vice versa. A team can therefore prioritize migration calmly instead of rewriting every component under time pressure.

7. Combining It with useImperativeHandle

Components that want to expose a curated, imperative API instead of a raw DOM element, say only a focus() and a reset() method instead of the full input element, still use useImperativeHandle for that. The hook itself has not changed functionally, but under React 19 it no longer needs forwardRef as a carrier component and can be used directly inside a plain function component with a ref prop.

This trims the boilerplate for imperative component APIs down to the essentials: props destructuring with ref, a useRef for the internal element, and a useImperativeHandle call that exposes the desired methods on the forwarded ref. For more complex form components with several imperative methods, this pattern stays considerably clearer than the two-layer forwardRef version from React 18.


function FancyInput({ ref, ...props }) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current?.focus(),
    clear: () => {
      if (inputRef.current) inputRef.current.value = "";
    },
  }));

  return <input ref={inputRef} {...props} />;
}

8. Linting Pitfalls and ref Cleanup Functions

Older ESLint configurations using the react-hooks plugin rule may not yet recognize the new ref-as-prop pattern and incorrectly flag ref as an unused or misplaced prop; updating to a current plugin version reliably fixes this. Another pitfall is the order of props destructuring: ref should never accidentally end up part of a rest spread that gets passed unfiltered to an underlying element, or ref ends up duplicated in the DOM.

React 19 also introduces ref cleanup functions: a ref callback can now itself return a function that gets called automatically on unmount or when the ref changes, closely mirroring the familiar useEffect cleanup pattern. Combined with ref as a plain prop, setup and teardown logic for a DOM element, say registering and removing a native event listener, can be attached directly to the ref callback itself without an additional useEffect.

9. When Migration Is Worth It

For new components there is practically no reason left to use forwardRef, the new pattern is shorter, easier to type, and functionally identical. For existing components, priority depends on how often they get touched: components already being reworked can be migrated in the same pass, while stable, rarely changed components can happily keep running with forwardRef.

A runtime or bundle size difference between the two patterns is practically unmeasurable, the benefit of migrating lies entirely in readability, simpler typing, and less boilerplate, not in performance. Teams should therefore treat the migration as a code quality measure that fits naturally into refactors already planned, rather than as an isolated, urgent task.

Aspect forwardRef (React 18) ref as a prop (React 19) Assessment
Boilerplate Extra wrapper and second signature Plain function component Less code in React 19
TypeScript typing forwardRef generics ref directly in the props interface Simpler in React 19
Compatibility Works in React 18 and 19 React 19 only forwardRef needed for React 18 support
useImperativeHandle Usable only inside forwardRef Directly in a plain component Less nesting in React 19
Runtime behavior Identical Identical No performance difference

Mironsoft

React architecture, performance, and Magento frontend integration

React frontends that stay fast instead of slowing down with every feature?

We review existing React applications for unnecessary re-renders, bloated bundles, and fragile state management, then build a frontend that stays performant and connects cleanly to Magento or other backends.

Performance Audit

Systematically measuring and fixing re-renders, bundle size, and load times.

State Architecture

Cleanly separating context, client state, and server state instead of mixing everything.

Magento Integration

Building robust, type-safe GraphQL or REST integration with Magento.

10. Summary

ref as a Prop in React 19: The Essentials at a Glance

Core change

ref is passed straight through to function components like any other prop in React 19, forwardRef has become optional.

Migration

Remove the wrapper, add ref to the props destructuring, the component body stays unchanged.

Compatibility

forwardRef keeps working, mixed codebases using both patterns coexist without issue.

TypeScript

ref can be typed directly in the props interface, the cumbersome forwardRef generics pair disappears.

11. FAQ: ref as a Prop in React 19: The Essentials at a Glance

1Do I have to remove forwardRef from my code right away?
No, forwardRef remains fully functional in React 19 and is only marked as deprecated. A gradual migration as part of changes already planned is entirely sufficient, there is no acute pressure to act.
2Does ref as a prop also work in React 18?
No, the new behavior was introduced specifically in React 19. Projects that need to support React 18, say because of a library dependency, should keep using forwardRef for their own components.
3Can ref now accidentally end up in a rest spread and get rendered twice?
Yes, that is a real risk if ref is not explicitly pulled out of the destructuring before the rest is spread onto an underlying element. ref should therefore always be destructured as its own named field, never part of the rest object.
4How does ref as a prop behave with class components?
Class components were never affected by the forwardRef requirement, ref on a class component has always pointed directly to the instance. The React 19 change affects function components exclusively.
5Do I need a new React package or Babel plugin for ref as a prop?
No, this is a pure runtime change in React itself starting with version 19, no additional build tools or plugins are required. A simple version update of react and react-dom is enough.
6What happens to TypeScript types for components using React.FC?
React.FC never automatically included a ref prop, that always had to be added manually beforehand. Under React 19, ref can simply be added to your own props interface like any other prop, regardless of whether React.FC is used or not.
7Is migration more complicated for components with multiple child elements?
No, the complexity does not depend on the number of child elements, only on how many different ref forwards exist inside the component. Each individual forward is migrated independently, regardless of the rest of the JSX structure.
8Can a component accept multiple refs as props?
Yes, any number of differently named ref props can be defined, say inputRef and containerRef, as long as they are not literally named ref and thereby collide with React's special mechanism. Only a prop named exactly ref receives special handling from React.
9Does ref as a prop also solve the ref problem with memo-wrapped components?
Yes, previously a memoized component additionally had to be combined with forwardRef to forward ref correctly. Under React 19, memo alone is enough, because ref already arrives as a regular prop at the level of the underlying function component.
10Is there an automated codemod for the migration?
The React team's recommendation is a gradual, manual migration, since forwardRef calls can differ quite a bit structurally. Community-driven codemod scripts exist for simple cases, but they should be verified against test code before being applied.