Claude-skill-registry-data migrating-from-forwardref
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.
git clone https://github.com/majiayu000/claude-skill-registry-data
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/migrating-from-forwardref" ~/.claude/skills/majiayu000-claude-skill-registry-data-migrating-from-forwardref && rm -rf "$T"
data/migrating-from-forwardref/SKILL.mdMigrating from forwardRef to Ref as Prop
<role> This skill teaches you how to migrate from the deprecated `forwardRef` API to React 19's ref-as-prop pattern. </role> <when-to-activate> This skill activates when:- User mentions
, refs, or ref forwardingforwardRef - Seeing code that uses
React.forwardRef - Upgrading components to React 19
- Need to expose DOM refs from custom components
- TypeScript errors about ref props </when-to-activate>
Why the Change:
- Simpler API - Refs are just props, no special wrapper needed
- Better TypeScript - Easier type inference and typing
- Consistency - All props handled the same way
- Less Boilerplate - Fewer imports and wrapper functions
Migration Path:
still works in React 19 (deprecated, not removed)forwardRef- New code should use ref as prop
- Gradual migration recommended for existing codebases
Key Difference:
</overview> <workflow> ## Migration Process// OLD: forwardRef (deprecated) const Button = forwardRef((props, ref) => ...); // NEW: ref as prop (React 19) function Button({ ref, ...props }) { ... }
Step 1: Identify forwardRef Usage
Search codebase for
forwardRef:
# Use Grep tool pattern: "forwardRef" output_mode: "files_with_matches"
Step 2: Understand Current Pattern
Before (React 18):
import { forwardRef } from 'react'; const MyButton = forwardRef((props, ref) => { return ( <button ref={ref} className={props.className}> {props.children} </button> ); });
Step 3: Convert to Ref as Prop
After (React 19):
function MyButton({ children, className, ref }) { return ( <button ref={ref} className={className}> {children} </button> ); }
Step 4: Update TypeScript Types (if applicable)
Before:
import { forwardRef } from 'react'; interface ButtonProps { variant: 'primary' | 'secondary'; children: React.ReactNode; } const Button = forwardRef<HTMLButtonElement, ButtonProps>( ({ variant, children }, ref) => { return ( <button ref={ref} className={variant}> {children} </button> ); } );
After:
import { Ref } from 'react'; interface ButtonProps { variant: 'primary' | 'secondary'; children: React.ReactNode; ref?: Ref<HTMLButtonElement>; } function Button({ variant, children, ref }: ButtonProps) { return ( <button ref={ref} className={variant}> {children} </button> ); }
Step 5: Test Component
Verify ref forwarding still works:
</workflow> <conditional-workflows> ## Complex Scenariosfunction Parent() { const buttonRef = useRef(null); useEffect(() => { buttonRef.current?.focus(); }, []); return <Button ref={buttonRef}>Click me</Button>; }
If component uses useImperativeHandle:
Before:
const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), clear: () => { inputRef.current.value = ''; } })); return <input ref={inputRef} />; });
After:
function FancyInput({ ref }) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), clear: () => { inputRef.current.value = ''; } })); return <input ref={inputRef} />; }
If component has multiple refs:
function ComplexComponent({ ref, innerRef, ...props }) { return ( <div ref={ref}> <input ref={innerRef} {...props} /> </div> ); }
If using generic components:
</conditional-workflows> <progressive-disclosure> ## Reference Filesinterface GenericProps<T> { value: T; ref?: Ref<HTMLDivElement>; } function GenericComponent<T>({ value, ref }: GenericProps<T>) { return <div ref={ref}>{String(value)}</div>; }
For detailed information:
- Ref Cleanup Functions: See
(lines 1013-1033)../../../research/react-19-comprehensive.md - useImperativeHandle: See
(lines 614-623)../../../research/react-19-comprehensive.md - TypeScript Migration: See
(lines 890-916)../../../research/react-19-comprehensive.md - Complete Migration Guide: See
(lines 978-1011)../../../research/react-19-comprehensive.md
Load references when specific patterns are needed. </progressive-disclosure>
<examples> ## Example 1: Simple Button MigrationBefore (React 18 with forwardRef):
import { forwardRef } from 'react'; const Button = forwardRef((props, ref) => ( <button ref={ref} {...props}> {props.children} </button> )); Button.displayName = 'Button'; export default Button;
After (React 19 with ref prop):
function Button({ children, ref, ...props }) { return ( <button ref={ref} {...props}> {children} </button> ); } export default Button;
Changes Made:
- ✅ Removed
importforwardRef - ✅ Removed
wrapperforwardRef - ✅ Added
to props destructuringref - ✅ Removed unnecessary
displayName - ✅ Simplified function signature
Example 2: TypeScript Component with Multiple Props
Before:
import { forwardRef, HTMLAttributes } from 'react'; interface CardProps extends HTMLAttributes<HTMLDivElement> { title: string; description?: string; variant?: 'default' | 'outlined'; } const Card = forwardRef<HTMLDivElement, CardProps>( ({ title, description, variant = 'default', ...props }, ref) => { return ( <div ref={ref} className={`card card-${variant}`} {...props}> <h3>{title}</h3> {description && <p>{description}</p>} </div> ); } ); Card.displayName = 'Card'; export default Card;
After:
import { Ref, HTMLAttributes } from 'react'; interface CardProps extends HTMLAttributes<HTMLDivElement> { title: string; description?: string; variant?: 'default' | 'outlined'; ref?: Ref<HTMLDivElement>; } function Card({ title, description, variant = 'default', ref, ...props }: CardProps) { return ( <div ref={ref} className={`card card-${variant}`} {...props}> <h3>{title}</h3> {description && <p>{description}</p>} </div> ); } export default Card;
Changes Made:
- ✅ Changed import from
toforwardRef
typeRef - ✅ Added
to interfaceref?: Ref<HTMLDivElement> - ✅ Removed
wrapperforwardRef - ✅ Added
to props destructuringref - ✅ Removed
displayName
Example 3: Input with useImperativeHandle
Before:
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 ( <input ref={inputRef} type="text" placeholder="Search..." {...props} /> ); }); export default SearchInput;
After:
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 ( <input ref={inputRef} type="text" placeholder="Search..." {...props} /> ); } export default SearchInput;
Usage (unchanged):
function SearchBar() { const searchRef = useRef(); const handleClear = () => { searchRef.current?.clear(); }; return ( <> <SearchInput ref={searchRef} /> <button onClick={handleClear}>Clear</button> </> ); }
Example 4: Component Library Pattern
Before:
import { forwardRef, ComponentPropsWithoutRef, ElementRef } from 'react'; type ButtonElement = ElementRef<'button'>; type ButtonProps = ComponentPropsWithoutRef<'button'> & { variant?: 'primary' | 'secondary'; }; const Button = forwardRef<ButtonElement, ButtonProps>( ({ variant = 'primary', className, ...props }, ref) => { return ( <button ref={ref} className={`btn btn-${variant} ${className || ''}`} {...props} /> ); } ); Button.displayName = 'Button';
After:
</examples> <constraints> ## MUSTimport { Ref, ComponentPropsWithoutRef, ElementRef } from 'react'; type ButtonElement = ElementRef<'button'>; type ButtonProps = ComponentPropsWithoutRef<'button'> & { variant?: 'primary' | 'secondary'; ref?: Ref<ButtonElement>; }; function Button({ variant = 'primary', className, ref, ...props }: ButtonProps) { return ( <button ref={ref} className={`btn btn-${variant} ${className || ''}`} {...props} /> ); }
- Add
to props interface when using TypeScriptref - Use
type from React for TypeScriptRef<HTMLElement> - Test that ref forwarding works after migration
- Maintain component behavior exactly (only syntax changes)
SHOULD
- Migrate components gradually (forwardRef still works)
- Update tests to verify ref behavior
- Use consistent prop ordering (ref near other element props)
- Document breaking changes if part of public API
NEVER
- Remove
if still on React 18forwardRef - Change component behavior during migration
- Break existing ref usage in parent components
- Skip TypeScript type updates for ref prop
-
Verify Ref Forwarding:
const ref = useRef(null); <MyComponent ref={ref} /> // ref.current should be the DOM element -
Check TypeScript Compilation:
npx tsc --noEmitNo errors about ref props
-
Test Component Behavior:
- Component renders correctly
- Ref accesses correct DOM element
- useImperativeHandle methods work (if used)
- No console warnings about deprecated APIs
-
Verify Backward Compatibility:
- Existing usage still works
- No breaking changes to component API
- Tests pass
Migration Checklist
When migrating a component from forwardRef:
- Remove
importforwardRef - Remove
wrapper functionforwardRef - Add
to props destructuringref - Add
type to TypeScript interface (if applicable)ref - Remove
if only used for forwardRefdisplayName - Test ref forwarding works
- Update component tests
- Check TypeScript compilation
- Verify no breaking changes to API
Common Migration Patterns
Pattern 1: Simple Ref Forwarding
// Before const Comp = forwardRef((props, ref) => <div ref={ref} />); // After function Comp({ ref }) { return <div ref={ref} />; }
Pattern 2: With useImperativeHandle
// Before const Comp = forwardRef((props, ref) => { useImperativeHandle(ref, () => ({ method() {} })); return <div />; }); // After function Comp({ ref }) { useImperativeHandle(ref, () => ({ method() {} })); return <div />; }
Pattern 3: TypeScript with Generics
// Before const Comp = forwardRef<HTMLDivElement, Props>((props, ref) => ...); // After function Comp({ ref, ...props }: Props & { ref?: Ref<HTMLDivElement> }) { ... }
For comprehensive forwardRef migration documentation, see:
research/react-19-comprehensive.md lines 978-1033.
Ref Cleanup Functions (New in React 19)
React 19 supports cleanup functions in ref callbacks:
<div ref={(node) => { console.log('Connected:', node); return () => { console.log('Disconnected:', node); }; }} />
When Cleanup Runs:
- Component unmounts
- Ref changes to different element
This works with both ref-as-prop and the old forwardRef pattern.