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
refprop - 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
inputprops - 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
RefObjectinstead ofRef:RefObjectonly covers mutable ref objects, whileRefsupports 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 whereTis the DOM element type - Prefer
ComponentProps<'tag'>when wrapping native elements - Always mark
refprops as optional withref? - Handle legacy scenarios with manual ref assignment
- Migrate from
forwardRefto directrefprops 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.