Ref as Prop - Pass refs as regular props without forwardRef
Recipe
// React 19: ref is just a prop
function TextInput({ placeholder, ref }: { placeholder: string; ref?: React.Ref<HTMLInputElement> }) {
return <input placeholder={placeholder} ref={ref} />;
}
// Usage -- identical to before
import { useRef } from "react";
function App() {
const inputRef = useRef<HTMLInputElement>(null);
return (
<div>
<TextInput ref={inputRef} placeholder="Type here..." />
<button onClick={() => inputRef.current?.focus()}>Focus</button>
</div>
);
}When to reach for this: Use this pattern for any function component that needs to expose a DOM node or imperative handle to a parent. It replaces forwardRef entirely.
Working Example
// A reusable input component library with ref support
"use client";
import { useRef, useImperativeHandle, type Ref, type ReactNode } from "react";
// Simple ref forwarding -- no forwardRef wrapper needed
function Input({
label,
error,
ref,
...inputProps
}: {
label: string;
error?: string;
ref?: Ref<HTMLInputElement>;
} & React.InputHTMLAttributes<HTMLInputElement>) {
return (
<div className="flex flex-col gap-1">
<label className="text-sm font-medium">{label}</label>
<input
ref={ref}
className={`border rounded p-2 ${error ? "border-red-500" : "border-gray-300"}`}
{...inputProps}
/>
{error && <span className="text-red-500 text-sm">{error}</span>}
</div>
);
}
// Imperative handle with ref -- also no forwardRef needed
type ComboboxHandle = {
open: () => void;
close: () => void;
reset: () => void;
};
function Combobox({
options,
ref,
}: {
options: string[];
ref?: Ref<ComboboxHandle>;
}) {
const inputRef = useRef<HTMLInputElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [value, setValue] = useState("");
useImperativeHandle(ref, () => ({
open: () => setIsOpen(true),
close: () => setIsOpen(false),
reset: () => {
setValue("");
setIsOpen(false);
},
}));
return (
<div className="relative">
<input
ref={inputRef}
value={value}
onChange={(e) => setValue(e.target.value)}
onFocus={() => setIsOpen(true)}
/>
{isOpen && (
<ul className="absolute top-full left-0 border bg-white w-full">
{options
.filter((o) => o.toLowerCase().includes(value.toLowerCase()))
.map((option) => (
<li
key={option}
onClick={() => {
setValue(option);
setIsOpen(false);
}}
className="p-2 hover:bg-gray-100 cursor-pointer"
>
{option}
</li>
))}
</ul>
)}
</div>
);
}
// Parent using both components
import { useState } from "react";
export default function RegistrationForm() {
const nameRef = useRef<HTMLInputElement>(null);
const comboRef = useRef<ComboboxHandle>(null);
function handleReset() {
nameRef.current?.focus();
comboRef.current?.reset();
}
return (
<form className="space-y-4">
<Input ref={nameRef} label="Full Name" placeholder="Jane Doe" />
<Combobox ref={comboRef} options={["Engineering", "Design", "Product", "Marketing"]} />
<button type="button" onClick={handleReset}>
Reset & Focus Name
</button>
</form>
);
}What this demonstrates:
- A simple
Inputcomponent receivingrefas a regular prop and forwarding it to the native<input> - A
Comboboxcomponent usinguseImperativeHandlewithrefas a prop to expose custom methods - Parent components using
useRefto interact with child DOM nodes and imperative handles - No
forwardRefwrapper anywhere
Deep Dive
How It Works
- In React 19,
refis treated as a regular prop for function components. React no longer strips it out before passing props to the component. forwardRefstill works and is not removed, but it is deprecated. A future React version will remove it. React logs a deprecation warning in development mode.- The
refprop is included in the component's props type automatically when usingReact.ComponentProps<typeof MyComponent>. - Class components continue to use
refas before -- the ref points to the class instance. This change only affects function components. useImperativeHandle(ref, createHandle)continues to work exactly as before. The only difference is whererefcomes from -- a prop instead of the second argument offorwardRef.
Variations
Ref cleanup functions (new in React 19):
function MeasuredDiv({ ref }: { ref?: Ref<HTMLDivElement> }) {
return (
<div
ref={(node) => {
// Setup
if (node) {
console.log("Mounted:", node.getBoundingClientRect());
}
// Return cleanup function (new in React 19)
return () => {
console.log("Unmounted");
};
}}
>
Measured content
</div>
);
}Generic component with ref:
type ListProps<T> = {
items: T[];
renderItem: (item: T) => ReactNode;
ref?: Ref<HTMLUListElement>;
};
function List<T>({ items, renderItem, ref }: ListProps<T>) {
return (
<ul ref={ref}>
{items.map((item, i) => (
<li key={i}>{renderItem(item)}</li>
))}
</ul>
);
}Migration from forwardRef:
// Before (React 18)
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
return <input ref={ref} {...props} />;
});
// After (React 19)
function Input({ ref, ...props }: InputProps & { ref?: Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}TypeScript Notes
- Use
Ref<T>(notRefObject<T>) for the prop type:ref?: React.Ref<HTMLInputElement>. React.Ref<T>is a union ofRefCallback<T>,RefObject<T>, andnull. This covers all ref usage patterns.- When using
ComponentProps, therefprop is included automatically in React 19 types. - For imperative handles, type the ref as
Ref<MyHandle>whereMyHandleis your custom interface.
Gotchas
- forwardRef deprecation warning in dev -- Existing
forwardRefcode works but logs a warning. Fix: Migrate by movingreffrom the secondforwardRefargument into the props object. Run the React codemod:npx @react-codemod/cli forward-ref-as-prop. - Spreading props may pass ref unintentionally --
{...props}now includesrefif the parent passed one. Fix: Destructurerefexplicitly:const { ref, ...rest } = props. - Ref cleanup return value -- In React 19, ref callbacks can return a cleanup function. If your existing ref callback returns something other than a function or
undefined, React will warn. Fix: Ensure ref callbacks either return nothing or return a cleanup function. - Class component refs unchanged --
refon a class component still points to the instance, not a prop. Fix: No migration needed for class components. - Third-party component libraries -- Libraries still using
forwardRefinternally continue to work. You do not need to wait for them to migrate. Fix: No action required.
Alternatives
| Approach | When to choose |
|---|---|
ref as prop (React 19) | Default for all new function components |
forwardRef (deprecated) | Existing code that has not been migrated yet |
useImperativeHandle | When you need to expose a custom API instead of a DOM node |
| Callback ref | When you need to run logic on mount/unmount of the ref target |
| State-based communication | When parent-child communication should trigger re-renders instead of imperative calls |
FAQs
Why was forwardRef removed in favor of ref as a prop?
forwardRefadded unnecessary wrapper complexity and made component APIs harder to understand- In React 19,
refis treated as a regular prop for function components -- React no longer strips it out forwardRefstill works but is deprecated and will be removed in a future version
How do you migrate from forwardRef to ref-as-prop?
// Before (React 18)
const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
return <input ref={ref} {...props} />;
});
// After (React 19)
function Input({ ref, ...props }: Props & { ref?: Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}Does useImperativeHandle still work with ref as a prop?
- Yes.
useImperativeHandle(ref, createHandle)works exactly as before - The only difference is that
refcomes from props instead of the second argument offorwardRef - Use it to expose custom methods (e.g.,
open(),close(),reset()) to parent components
What are ref cleanup functions in React 19?
<div ref={(node) => {
if (node) console.log("Mounted:", node);
// Return cleanup function (new in React 19)
return () => console.log("Unmounted");
}} />Ref callbacks can now return a cleanup function, similar to useEffect cleanup.
Does ref-as-prop affect class components?
- No. Class components continue to use
refas before -- the ref points to the class instance - This change only affects function components
- No migration is needed for class components
Is there an automated codemod for migrating forwardRef?
- Yes. Run
npx @react-codemod/cli forward-ref-as-propto automatically migrate - It moves
reffrom theforwardRefsecond argument into the props destructuring - Review the output for any edge cases in your codebase
How do you write a generic component that accepts a ref?
type ListProps<T> = {
items: T[];
renderItem: (item: T) => ReactNode;
ref?: Ref<HTMLUListElement>;
};
function List<T>({ items, renderItem, ref }: ListProps<T>) {
return (
<ul ref={ref}>
{items.map((item, i) => <li key={i}>{renderItem(item)}</li>)}
</ul>
);
}Gotcha: What happens when you spread props that include ref?
{...props}now includesrefif the parent passed one, which may forward it unintentionally- Always destructure
refexplicitly:const { ref, ...rest } = props - Then apply
refonly where intended
Gotcha: My ref callback returns a value and React is warning. Why?
- In React 19, ref callbacks can return a cleanup function or
undefined - Returning anything else (e.g., a non-function value) triggers a warning
- Ensure your ref callback either returns nothing or explicitly returns a cleanup function
What is the correct TypeScript type for the ref prop?
- Use
React.Ref<T>(notRefObject<T>) for the prop type:ref?: React.Ref<HTMLInputElement> React.Ref<T>is a union ofRefCallback<T>,RefObject<T>, andnull- This covers callback refs, object refs, and null
How does ComponentProps work with ref in React 19 TypeScript types?
React.ComponentProps<typeof MyComponent>automatically includesrefin the type- You do not need to add
refmanually when extending component props - This is a change from React 18 where
refwas excluded fromComponentProps
Do third-party libraries using forwardRef still work in React 19?
- Yes. Libraries still using
forwardRefinternally continue to work without issues - You do not need to wait for libraries to migrate before upgrading your own code
forwardRefis deprecated but functional
Related
- Overview -- Full list of React 19 features
- Form Actions -- Forms often use refs for reset and focus
- Metadata -- Another API simplification in React 19