Typing React 19 ref as a Prop in TypeScript
Problem Statement
React 19 introduces a new "ref as prop" feature as an alternative to forwardRef
. This simplifies ref handling in function components by allowing direct access to the ref
prop. However, the official documentation only provides JavaScript examples, leaving TypeScript users uncertain about how to properly type these new ref
props.
When converting this pattern to TypeScript, developers face challenges:
- Determining the correct type for the
ref
prop - Handling optional refs correctly
- Maintaining compatibility with libraries not yet updated for React 19
- Managing additional props while including the ref
Recommended Solutions
1. Using Ref<T>
with Explicit Props
The most straightforward approach uses React's Ref<T>
type for the ref prop, where T
is the DOM element type:
import { Ref } from 'react';
function MyInput({ placeholder, ref }: {
placeholder?: string;
ref?: Ref<HTMLInputElement>;
}) {
return <input placeholder={placeholder} ref={ref} />;
}
WARNING
Always mark ref
as optional (?
). Components should work correctly even when not passed a ref.
Usage example:
function MyComponent() {
const inputRef = useRef<HTMLInputElement>(null);
return (
<MyInput
ref={inputRef}
placeholder="Type something..."
/>
);
}
2. Using ComponentProps
for Native Components
For components wrapping native HTML elements, use ComponentProps
to automatically inherit all native props:
import { ComponentProps } from 'react';
function MyInput(props: ComponentProps<'input'>) {
return <input {...props} />;
}
This approach:
- Automatically includes all valid
input
props - Handles ref typing internally
- Prevents missing props during future changes
- Scales well for complex components
function App() {
const inputRef = useRef<HTMLInputElement>(null);
return (
<MyInput
ref={inputRef}
type="text"
className="input-field"
placeholder="Type here..."
/>
);
}
Advanced Patterns
Handling Complex Library Interactions
For libraries that haven't yet adopted React 19's ref pattern, use manual ref assignment:
import { Ref } from 'react';
function RichTextEditor({ ref }: { ref?: Ref<LexicalEditor> }) {
// Library specific ref handling
const handleRef = (editorRef: LexicalEditor | null) => {
if (!ref) return;
// Handle both callback refs and mutable ref objects
if (typeof ref === 'function') {
ref(editorRef);
} else if (ref) {
ref.current = editorRef;
}
};
return <LexicalEditor refCallback={handleRef} />;
}
Migrating from Legacy forwardRef
To safely migrate existing components:
// Before (React 18)
const LegacyInput = forwardRef<HTMLInputElement, { placeholder: string }>(
({ placeholder }, ref) => (
<input placeholder={placeholder} ref={ref} />
)
);
// After (React 19)
function NewInput({ placeholder, ref }: {
placeholder: string;
ref?: Ref<HTMLInputElement>
}) {
return <input placeholder={placeholder} ref={ref} />;
}
Common Mistakes to Avoid
Using
RefObject
instead ofRef
:RefObject
only covers mutable ref objects, whileRef
supports both object and function refsMaking ref required:
Always markref?
as optional - not all components need refsIgnoring legacy integrations:
Components used with older libraries should handle both ref patterns
// 🚫 Incorrect
function WrongInput({ ref }: { ref: React.RefObject<HTMLDivElement> }) {
return <div ref={ref} />;
}
// ✅ Correct
function CorrectInput({ ref }: { ref?: React.Ref<HTMLDivElement> }) {
return <div ref={ref} />;
}
Key Takeaways
- Use
Ref<T>
for explicit ref typing whereT
is the DOM element type - Prefer
ComponentProps<'tag'>
when wrapping native elements - Always mark
ref
props as optional withref?
- Handle legacy scenarios with manual ref assignment
- Migrate from
forwardRef
to directref
props for simplified APIs
With these patterns, you'll safely integrate React 19's new ref convention while maintaining type safety.
Future-Proofing
While these types work with current React 19 betas, watch for official React type updates after release for potential refinements.