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.
Migrates React components from deprecated `forwardRef` to React 19's ref-as-prop pattern. Activates when encountering `forwardRef` usage, React 19 upgrades, or ref-related TypeScript errors.
/plugin marketplace add djankies/claude-configs/plugin install react-19@claude-configsThis skill is limited to using the following tools:
forwardRef, refs, or ref forwardingReact.forwardRefWhy the Change:
Migration Path:
forwardRef still works in React 19 (deprecated, not removed)Key Difference:
// OLD: forwardRef (deprecated)
const Button = forwardRef((props, ref) => ...);
// NEW: ref as prop (React 19)
function Button({ ref, ...props }) { ... }
</overview>
<workflow>
## Migration Process
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:
function Parent() {
const buttonRef = useRef(null);
useEffect(() => {
buttonRef.current?.focus();
}, []);
return <Button ref={buttonRef}>Click me</Button>;
}
</workflow>
<conditional-workflows>
## Complex Scenarios
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:
interface GenericProps<T> {
value: T;
ref?: Ref<HTMLDivElement>;
}
function GenericComponent<T>({ value, ref }: GenericProps<T>) {
return <div ref={ref}>{String(value)}</div>;
}
</conditional-workflows>
<progressive-disclosure>
## Reference Files
For detailed information:
../../../research/react-19-comprehensive.md (lines 1013-1033)../../../research/react-19-comprehensive.md (lines 614-623)../../../research/react-19-comprehensive.md (lines 890-916)../../../research/react-19-comprehensive.md (lines 978-1011)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:
forwardRef importforwardRef wrapperref to props destructuringdisplayNameBefore:
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:
forwardRef to Ref typeref?: Ref<HTMLDivElement> to interfaceforwardRef wrapperref to props destructuringdisplayNameBefore:
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>
</>
);
}
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:
import { 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}
/>
);
}
</examples>
<constraints>
## MUST
ref to props interface when using TypeScriptRef<HTMLElement> type from React for TypeScriptforwardRef if still on React 18Verify Ref Forwarding:
const ref = useRef(null);
<MyComponent ref={ref} />
// ref.current should be the DOM element
Check TypeScript Compilation:
npx tsc --noEmit
No errors about ref props
Test Component Behavior:
Verify Backward Compatibility:
When migrating a component from forwardRef:
forwardRef importforwardRef wrapper functionref to props destructuringref type to TypeScript interface (if applicable)displayName if only used for forwardRef// Before
const Comp = forwardRef((props, ref) => <div ref={ref} />);
// After
function Comp({ ref }) { return <div ref={ref} />; }
// Before
const Comp = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({ method() {} }));
return <div />;
});
// After
function Comp({ ref }) {
useImperativeHandle(ref, () => ({ method() {} }));
return <div />;
}
// 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.
React 19 supports cleanup functions in ref callbacks:
<div
ref={(node) => {
console.log('Connected:', node);
return () => {
console.log('Disconnected:', node);
};
}}
/>
When Cleanup Runs:
This works with both ref-as-prop and the old forwardRef pattern.
This skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.