Ref como prop - Pasa refs como props regulares sin forwardRef
Receta
// React 19: ref es solo una prop
function TextInput({ placeholder, ref }: { placeholder: string; ref?: React.Ref<HTMLInputElement> }) {
return <input placeholder={placeholder} ref={ref} />;
}
// Uso -- idéntico a antes
import { useRef } from "react";
function App() {
const inputRef = useRef<HTMLInputElement>(null);
return (
<div>
<TextInput ref={inputRef} placeholder="Escribe aquí..." />
<button onClick={() => inputRef.current?.focus()}>Enfocar</button>
</div>
);
}Cuándo usarlo: Usa este patrón para cualquier componente funcional que necesite exponer un nodo DOM o un manejador imperativo a un componente padre. Reemplaza forwardRef por completo.
Ejemplo funcional
// Una librería de componentes input reutilizable con soporte de ref
"use client";
import { useRef, useImperativeHandle, type Ref, type ReactNode } from "react";
// Reenvío simple de ref -- sin necesidad del wrapper forwardRef
function Input({
label,
error,
ref,
...inputProps
}: {
label: string;
error?: string;
ref?: Ref<HTMLInputElement>;
} & React.InputHTMLAttributes<HTMLInputElement>) {
return (
<div className="flex flex-col gap-1">
<label className="text-sm font-medium">{label}</label>
<input
ref={ref}
className={`border rounded p-2 ${error ? "border-red-500" : "border-gray-300"}`}
{...inputProps}
/>
{error && <span className="text-red-500 text-sm">{error}</span>}
</div>
);
}
// Manejador imperativo con ref -- tampoco necesita forwardRef
type ComboboxHandle = {
open: () => void;
close: () => void;
reset: () => void;
};
function Combobox({
options,
ref,
}: {
options: string[];
ref?: Ref<ComboboxHandle>;
}) {
const inputRef = useRef<HTMLInputElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [value, setValue] = useState("");
useImperativeHandle(ref, () => ({
open: () => setIsOpen(true),
close: () => setIsOpen(false),
reset: () => {
setValue("");
setIsOpen(false);
},
}));
return (
<div className="relative">
<input
ref={inputRef}
value={value}
onChange={(e) => setValue(e.target.value)}
onFocus={() => setIsOpen(true)}
/>
{isOpen && (
<ul className="absolute top-full left-0 border bg-white w-full">
{options
.filter((o) => o.toLowerCase().includes(value.toLowerCase()))
.map((option) => (
<li
key={option}
onClick={() => {
setValue(option);
setIsOpen(false);
}}
className="p-2 hover:bg-gray-100 cursor-pointer"
>
{option}
</li>
))}
</ul>
)}
</div>
);
}
// Componente padre usando ambos componentes
import { useState } from "react";
export default function RegistrationForm() {
const nameRef = useRef<HTMLInputElement>(null);
const comboRef = useRef<ComboboxHandle>(null);
function handleReset() {
nameRef.current?.focus();
comboRef.current?.reset();
}
return (
<form className="space-y-4">
<Input ref={nameRef} label="Nombre completo" placeholder="Jane Doe" />
<Combobox ref={comboRef} options={["Ingeniería", "Diseño", "Producto", "Marketing"]} />
<button type="button" onClick={handleReset}>
Reiniciar y enfocar nombre
</button>
</form>
);
}Lo que esto demuestra:
- Un componente
Inputsimple recibiendorefcomo una prop regular y reenviándolo al<input>nativo - Un componente
ComboboxusandouseImperativeHandleconrefcomo una prop para exponer métodos personalizados - Componentes padre usando
useRefpara interactuar con nodos DOM y manejadores imperativos de componentes hijo - Sin wrapper
forwardRefen ningún lugar
Análisis profundo
Cómo funciona
- En React 19,
refse trata como una prop regular para componentes funcionales. React ya no lo elimina antes de pasar props al componente. forwardRefaún funciona y no se ha removido, pero está deprecado. Una versión futura de React lo eliminará. React registra una advertencia de deprecación en modo desarrollo.- La prop
refse incluye automáticamente en el tipo de props del componente cuando usasReact.ComponentProps<typeof MyComponent>. - Los componentes de clase continúan usando
refcomo antes -- el ref apunta a la instancia de la clase. Este cambio solo afecta a componentes funcionales. useImperativeHandle(ref, createHandle)continúa funcionando exactamente como antes. La única diferencia es de dónde vieneref-- una prop en lugar del segundo argumento deforwardRef.
Variaciones
Funciones de limpieza de ref (nuevas en React 19):
function MeasuredDiv({ ref }: { ref?: Ref<HTMLDivElement> }) {
return (
<div
ref={(node) => {
// Configuración
if (node) {
console.log("Montado:", node.getBoundingClientRect());
}
// Devuelve función de limpieza (nueva en React 19)
return () => {
console.log("Desmontado");
};
}}
>
Contenido medido
</div>
);
}Componente genérico con ref:
type ListProps<T> = {
items: T[];
renderItem: (item: T) => ReactNode;
ref?: Ref<HTMLUListElement>;
};
function List<T>({ items, renderItem, ref }: ListProps<T>) {
return (
<ul ref={ref}>
{items.map((item, i) => (
<li key={i}>{renderItem(item)}</li>
))}
</ul>
);
}Migración desde forwardRef:
// Antes (React 18)
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
return <input ref={ref} {...props} />;
});
// Después (React 19)
function Input({ ref, ...props }: InputProps & { ref?: Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}Notas de TypeScript
- Usa
Ref<T>(noRefObject<T>) para el tipo de prop:ref?: React.Ref<HTMLInputElement>. React.Ref<T>es una unión deRefCallback<T>,RefObject<T>, ynull. Esto cubre todos los patrones de uso de ref.- Cuando usas
ComponentProps, la proprefse incluye automáticamente en los tipos de React 19. - Para manejadores imperativos, tipea el ref como
Ref<MyHandle>dondeMyHandlees tu interfaz personalizada.
Trampas
- Advertencia de deprecación de forwardRef en desarrollo -- El código
forwardRefexistente funciona pero registra una advertencia. Solución: Migra moviendorefdel segundo argumento deforwardRefal objeto props. Ejecuta el codemod de React:npx @react-codemod/cli forward-ref-as-prop. - Expandir props puede pasar ref sin intención --
{...props}ahora incluyerefsi el componente padre pasó uno. Solución: Desestructurarefexplícitamente:const { ref, ...rest } = props. - Valor de retorno de limpieza de ref -- En React 19, los callbacks de ref pueden devolver una función de limpieza. Si tu callback de ref existente devuelve algo distinto a una función o
undefined, React avisará. Solución: Asegúrate de que los callbacks de ref devuelvan nada o devuelvan explícitamente una función de limpieza. - Refs de componentes de clase sin cambios --
refen un componente de clase aún apunta a la instancia, no a una prop. Solución: No se necesita migración para componentes de clase. - Librerías de componentes de terceros -- Las librerías que aún usan
forwardRefinternamente continúan funcionando. No necesitas esperar a que migren. Solución: No se requiere acción.
Alternativas
| Enfoque | Cuándo elegirlo |
|---|---|
ref como prop (React 19) | Predeterminado para todos los componentes funcionales nuevos |
forwardRef (deprecado) | Código existente que aún no se ha migrado |
useImperativeHandle | Cuando necesites exponer una API personalizada en lugar de un nodo DOM |
| Callback ref | Cuando necesites ejecutar lógica en montaje/desmontaje del objetivo del ref |
| Comunicación basada en state | Cuando la comunicación padre-hijo debe provocar re-renderizados en lugar de llamadas imperativas |
FAQs
¿Por qué se removió forwardRef en favor de ref como una prop?
forwardRefañadía complejidad innecesaria de wrapper y hacía las APIs de componentes más difíciles de entender- En React 19,
refse trata como una prop regular para componentes funcionales -- React ya no lo elimina forwardRefaún funciona pero está deprecado y se eliminará en una versión futura
¿Cómo migas de forwardRef a ref-as-prop?
// Antes (React 18)
const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
return <input ref={ref} {...props} />;
});
// Después (React 19)
function Input({ ref, ...props }: Props & { ref?: Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}¿Sigue funcionando useImperativeHandle con ref como una prop?
- Sí.
useImperativeHandle(ref, createHandle)funciona exactamente como antes - La única diferencia es que
refviene de props en lugar del segundo argumento deforwardRef - Úsalo para exponer métodos personalizados (p. ej.,
open(),close(),reset()) a componentes padre
¿Qué son las funciones de limpieza de ref en React 19?
<div ref={(node) => {
if (node) console.log("Montado:", node);
// Devuelve función de limpieza (nueva en React 19)
return () => console.log("Desmontado");
}} />Los callbacks de ref ahora pueden devolver una función de limpieza, similar a la limpieza de useEffect.
¿Afecta ref-as-prop a los componentes de clase?
- No. Los componentes de clase continúan usando
refcomo antes -- el ref apunta a la instancia de la clase - Este cambio solo afecta a componentes funcionales
- No se requiere migración para componentes de clase
¿Hay un codemod automatizado para migrar forwardRef?
- Sí. Ejecuta
npx @react-codemod/cli forward-ref-as-proppara migrar automáticamente - Mueve
refdel segundo argumento deforwardRefa la desestructuración de props - Revisa el resultado para detectar casos especiales en tu base de código
¿Cómo escribes un componente genérico que acepta un ref?
type ListProps<T> = {
items: T[];
renderItem: (item: T) => ReactNode;
ref?: Ref<HTMLUListElement>;
};
function List<T>({ items, renderItem, ref }: ListProps<T>) {
return (
<ul ref={ref}>
{items.map((item, i) => <li key={i}>{renderItem(item)}</li>)}
</ul>
);
}Trampa: ¿Qué pasa cuando expandes props que incluyen ref?
{...props}ahora incluyerefsi el componente padre lo pasó, lo que puede reenviarlo sin intención- Siempre desestructura
refexplícitamente:const { ref, ...rest } = props - Luego aplica
refsolo donde sea necesario
Trampa: Mi callback de ref devuelve un valor y React está avisando. ¿Por qué?
- En React 19, los callbacks de ref pueden devolver una función de limpieza o
undefined - Devolver cualquier otra cosa (p. ej., un valor no función) activa una advertencia
- Asegúrate de que tu callback de ref devuelva nada o explícitamente una función de limpieza
¿Cuál es el tipo TypeScript correcto para la prop ref?
- Usa
React.Ref<T>(noRefObject<T>) para el tipo de prop:ref?: React.Ref<HTMLInputElement> React.Ref<T>es una unión deRefCallback<T>,RefObject<T>, ynull- Esto cubre refs de callback, refs de objeto, y null
¿Cómo funciona ComponentProps con ref en tipos TypeScript de React 19?
React.ComponentProps<typeof MyComponent>incluye automáticamenterefen el tipo- No necesitas añadir
refmanualmente cuando extiendes las props de componentes - Este es un cambio desde React 18 donde
refse excluía deComponentProps
¿Siguen funcionando las librerías de terceros usando forwardRef en React 19?
- Sí. Las librerías que aún usan
forwardRefinternamente continúan funcionando sin problemas - No necesitas esperar a que las librerías migren antes de actualizar tu propio código
forwardRefestá deprecado pero funcional
Relacionado
- Descripción general -- Lista completa de características de React 19
- Form Actions -- Los formularios a menudo usan refs para reinicio y enfoque
- Metadata -- Otra simplificación de API en React 19