--- name: migrating-from-forwardref description: Teaches migration from forwardRef to ref-as-prop pattern in React 19. Use when seeing forwardRef usage, upgrading React components, or when refs are mentioned. forwardRef is deprecated in React 19. allowed-tools: Read, Write, Edit, Glob, Grep version: 1.0.0 --- # Migrating from forwardRef to Ref as Prop This skill teaches you how to migrate from the deprecated `forwardRef` API to React 19's ref-as-prop pattern. This skill activates when: - User mentions `forwardRef`, refs, or ref forwarding - Seeing code that uses `React.forwardRef` - Upgrading components to React 19 - Need to expose DOM refs from custom components - TypeScript errors about ref props React 19 deprecates `forwardRef` in favor of ref as a regular prop: **Why the Change:** 1. **Simpler API** - Refs are just props, no special wrapper needed 2. **Better TypeScript** - Easier type inference and typing 3. **Consistency** - All props handled the same way 4. **Less Boilerplate** - Fewer imports and wrapper functions **Migration Path:** - `forwardRef` still works in React 19 (deprecated, not removed) - New code should use ref as prop - Gradual migration recommended for existing codebases **Key Difference:** ```javascript // OLD: forwardRef (deprecated) const Button = forwardRef((props, ref) => ...); // NEW: ref as prop (React 19) function Button({ ref, ...props }) { ... } ``` ## Migration Process **Step 1: Identify forwardRef Usage** Search codebase for `forwardRef`: ```bash # Use Grep tool pattern: "forwardRef" output_mode: "files_with_matches" ``` **Step 2: Understand Current Pattern** Before (React 18): ```javascript import { forwardRef } from 'react'; const MyButton = forwardRef((props, ref) => { return ( ); }); ``` **Step 3: Convert to Ref as Prop** After (React 19): ```javascript function MyButton({ children, className, ref }) { return ( ); } ``` **Step 4: Update TypeScript Types (if applicable)** Before: ```typescript import { forwardRef } from 'react'; interface ButtonProps { variant: 'primary' | 'secondary'; children: React.ReactNode; } const Button = forwardRef( ({ variant, children }, ref) => { return ( ); } ); ``` After: ```typescript import { Ref } from 'react'; interface ButtonProps { variant: 'primary' | 'secondary'; children: React.ReactNode; ref?: Ref; } function Button({ variant, children, ref }: ButtonProps) { return ( ); } ``` **Step 5: Test Component** Verify ref forwarding still works: ```javascript function Parent() { const buttonRef = useRef(null); useEffect(() => { buttonRef.current?.focus(); }, []); return ; } ``` ## Complex Scenarios **If component uses useImperativeHandle:** Before: ```javascript const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), clear: () => { inputRef.current.value = ''; } })); return ; }); ``` After: ```javascript function FancyInput({ ref }) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), clear: () => { inputRef.current.value = ''; } })); return ; } ``` **If component has multiple refs:** ```javascript function ComplexComponent({ ref, innerRef, ...props }) { return (
); } ``` **If using generic components:** ```typescript interface GenericProps { value: T; ref?: Ref; } function GenericComponent({ value, ref }: GenericProps) { return
{String(value)}
; } ```
## Reference Files For detailed information: - **Ref Cleanup Functions**: See `../../../research/react-19-comprehensive.md` (lines 1013-1033) - **useImperativeHandle**: See `../../../research/react-19-comprehensive.md` (lines 614-623) - **TypeScript Migration**: See `../../../research/react-19-comprehensive.md` (lines 890-916) - **Complete Migration Guide**: See `../../../research/react-19-comprehensive.md` (lines 978-1011) Load references when specific patterns are needed. ## Example 1: Simple Button Migration **Before (React 18 with forwardRef):** ```javascript import { forwardRef } from 'react'; const Button = forwardRef((props, ref) => ( )); Button.displayName = 'Button'; export default Button; ``` **After (React 19 with ref prop):** ```javascript function Button({ children, ref, ...props }) { return ( ); } export default Button; ``` **Changes Made:** 1. ✅ Removed `forwardRef` import 2. ✅ Removed `forwardRef` wrapper 3. ✅ Added `ref` to props destructuring 4. ✅ Removed unnecessary `displayName` 5. ✅ Simplified function signature ## Example 2: TypeScript Component with Multiple Props **Before:** ```typescript import { forwardRef, HTMLAttributes } from 'react'; interface CardProps extends HTMLAttributes { title: string; description?: string; variant?: 'default' | 'outlined'; } const Card = forwardRef( ({ title, description, variant = 'default', ...props }, ref) => { return (

{title}

{description &&

{description}

}
); } ); Card.displayName = 'Card'; export default Card; ``` **After:** ```typescript import { Ref, HTMLAttributes } from 'react'; interface CardProps extends HTMLAttributes { title: string; description?: string; variant?: 'default' | 'outlined'; ref?: Ref; } function Card({ title, description, variant = 'default', ref, ...props }: CardProps) { return (

{title}

{description &&

{description}

}
); } export default Card; ``` **Changes Made:** 1. ✅ Changed import from `forwardRef` to `Ref` type 2. ✅ Added `ref?: Ref` to interface 3. ✅ Removed `forwardRef` wrapper 4. ✅ Added `ref` to props destructuring 5. ✅ Removed `displayName` ## Example 3: Input with useImperativeHandle **Before:** ```javascript import { forwardRef, useRef, useImperativeHandle } from 'react'; const SearchInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus() { inputRef.current?.focus(); }, clear() { inputRef.current.value = ''; }, getValue() { return inputRef.current?.value || ''; } })); return ( ); }); export default SearchInput; ``` **After:** ```javascript import { useRef, useImperativeHandle } from 'react'; function SearchInput({ ref, ...props }) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus() { inputRef.current?.focus(); }, clear() { inputRef.current.value = ''; }, getValue() { return inputRef.current?.value || ''; } })); return ( ); } export default SearchInput; ``` **Usage (unchanged):** ```javascript function SearchBar() { const searchRef = useRef(); const handleClear = () => { searchRef.current?.clear(); }; return ( <> ); } ``` ## Example 4: Component Library Pattern **Before:** ```typescript import { forwardRef, ComponentPropsWithoutRef, ElementRef } from 'react'; type ButtonElement = ElementRef<'button'>; type ButtonProps = ComponentPropsWithoutRef<'button'> & { variant?: 'primary' | 'secondary'; }; const Button = forwardRef( ({ variant = 'primary', className, ...props }, ref) => { return (