Skip to content

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

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:

tsx
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:

tsx
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:

tsx
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
tsx
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:

tsx
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:

tsx
// 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

  1. Using RefObject instead of Ref:
    RefObject only covers mutable ref objects, while Ref supports both object and function refs

  2. Making ref required:
    Always mark ref? as optional - not all components need refs

  3. Ignoring legacy integrations:
    Components used with older libraries should handle both ref patterns

tsx
// 🚫 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 where T is the DOM element type
  • Prefer ComponentProps<'tag'> when wrapping native elements
  • Always mark ref props as optional with ref?
  • Handle legacy scenarios with manual ref assignment
  • Migrate from forwardRef to direct ref 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.