forwardRef permite que un componente padre pase una ref a través de tu componente hasta un elemento del DOM que está dentro, para que el padre pueda llamar a focus(), scrollIntoView() y métodos parecidos sobre ese elemento. En React 19 ya no lo necesitas: los componentes de función reciben ref como una prop normal. forwardRef sigue funcionando, y lo verás en la mayor parte del código existente, así que esta página muestra ambos.
forwardRef recibe una función de renderizado con dos argumentos: props, y la ref que pasó el padre. ref no está dentro de props; llega por separado. Haz clic en Focus y la consola confirma que inputRef.current es el nodo <input> real.
La forma de React 19: ref es una prop
Desde React 19 un componente de función recibe ref en sus props como cualquier otra prop. Sin envoltorio, sin segundo argumento:
Escribe algo y haz clic en Log value: el padre lee el texto actual del input directamente desde el nodo del DOM. Borra ref={ref} del <input> y haz clic en Focus: la consola muestra un error, porque inputRef.current ahora es null.
forwardRef (todas las versiones) | prop ref (React 19+) | |
|---|---|---|
| Cómo recibe la ref el componente | Segundo argumento, (props, ref) | props.ref |
| Necesita envoltorio | Sí | No |
Funciona con useImperativeHandle | Sí | Sí |
| Estado | Funciona, se planea declararlo obsoleto | La forma de escribir código nuevo |
Una ref que el hijo ignora se queda en null
Pasar ref a un componente no hace nada por sí solo. El componente tiene que ponerla en un elemento. Si no lo hace, ref.current se queda en null:
La consola muestra Forgetful ref: null y Careful ref: INPUT. Esta es la razón más común de "ref.current is null" en un componente propio. Antes de React 19, Careful también habría sido null a menos que estuviera envuelto en forwardRef, porque la ref nunca se entregaba a la función.
useImperativeHandle: expón una API más pequeña
Darle al padre el nodo del DOM completo significa que puede hacerle cualquier cosa: cambiar estilos, quitar hijos, leer valores que querías mantener privados. useImperativeHandle te permite decidir qué contiene ref.current:
El último botón registra ["focus", "clear"]: el padre recibe esos dos métodos y nada más, no el nodo del input. Agrega un método select() al objeto y un botón que lo llame, y el padre gana exactamente una capacidad más.
El hijo conserva su propia ref, inputRef, para el input real, y el handle la envuelve. El tercer argumento es un array de dependencias, como el de useEffect: el handle se reconstruye cuando esos valores cambian.
Úsalo con moderación. La mayoría de las cosas que quiere un padre (abrir, cerrar, mostrar un error) funcionan mejor como props, como isOpen o error. Los métodos imperativos son para acciones que no tienen una prop natural: dar foco, desplazar, reproducir un video, iniciar una animación.
Usar la ref también dentro del hijo
A veces el hijo necesita el mismo nodo del DOM para su propio trabajo, por ejemplo para medirlo o darle foco después de un error, mientras el padre también tiene una ref hacia él. Un atributo ref solo puede recibir un valor, así que combina las dos con una ref callback:
function AutoGrowTextarea({ ref, ...props }) {
const localRef = useRef(null);
function setRefs(node) {
localRef.current = node;
if (typeof ref === 'function') ref(node);
else if (ref) ref.current = node;
}
return <textarea ref={setRefs} {...props} />;
}
La ref del padre puede ser un objeto de useRef o una función, así que maneja ambos casos. Cuando el padre solo necesita algunas acciones, el useImperativeHandle de arriba es la opción más limpia, porque el hijo se queda el nodo para sí.
Pasar una ref a través de varias capas
Una ref viaja de componente en componente. Si Form renderiza Field, que renderiza TextInput, que renderiza el <input>, cada uno de esos componentes tiene que pasar ref al siguiente. En React 19 es una prop más que reenviar (<TextInput ref={ref} />); con forwardRef, cada capa tenía que estar envuelta. Propagar las props ({...props}) no la lleva en versiones anteriores, porque antes de React 19 ref nunca formaba parte de props.
Los componentes de un sistema de diseño son el lugar habitual donde esto importa: un Button, Input o Select que envuelve un elemento nativo debería reenviar su ref, para que la app que lo usa pueda darle foco, medirlo o entregarlo a una librería que posiciona popovers.
Migrar desde forwardRef
El cambio es mecánico: quita el envoltorio y lee ref desde las props.
// Before
const Button = forwardRef(function Button({ variant, ...props }, ref) {
return <button ref={ref} className={variant} {...props} />;
});
// After (React 19)
function Button({ variant, ref, ...props }) {
return <button ref={ref} className={variant} {...props} />;
}
No hay prisa. forwardRef sigue funcionando en React 19, y una librería que deba ser compatible con React 18 tiene que conservarlo, porque React 18 no pasa ref como prop. Los componentes de clase no se ven afectados: una ref en un componente de clase sigue apuntando a la instancia del componente.
TypeScript
Con forwardRef, los argumentos de tipo van en este orden: primero el tipo de la ref, luego las props:
import { forwardRef } from 'react';
type FancyInputProps = { label: string };
const FancyInput = forwardRef<HTMLInputElement, FancyInputProps>(
function FancyInput({ label }, ref) {
return <input ref={ref} aria-label={label} />;
}
);
En React 19, tipa ref como cualquier otra prop. ComponentProps<'input'> ya la incluye:
import { useImperativeHandle, useRef, type ComponentProps, type Ref } from 'react';
function FancyInput(props: ComponentProps<'input'>) {
return <input {...props} />;
}
type SearchHandle = { focus: () => void; clear: () => void };
function SearchBox({ ref }: { ref?: Ref<SearchHandle> }) {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => {
if (inputRef.current) inputRef.current.value = '';
},
}));
return <input ref={inputRef} />;
}
// In the parent
const searchRef = useRef<SearchHandle>(null);
La página de useRef cubre las refs en sí: acceso al DOM, valores que persisten sin renderizar y ref callbacks. Para tipar componentes en general, consulta React con TypeScript.
Preguntas frecuentes
¿Qué hace forwardRef en React?
Envuelve un componente de función para que una ref que da el padre llegue al componente como segundo argumento, (props, ref). Luego el componente pone esa ref en un nodo del DOM, para que el padre pueda llamar a métodos como focus() sobre él.
¿forwardRef está obsoleto en React 19?
Todavía no, y sigue funcionando. React 19 pasa ref a los componentes de función como una prop normal, así que el código nuevo no lo necesita, y el equipo de React dijo que planea declarar obsoleto forwardRef en una versión futura.
¿Por qué mi ref es null en un componente propio?
El componente recibió la ref pero no la puso en ningún elemento. Conéctala a un nodo del DOM interno: <input ref={ref} />. Antes de React 19 la ref ni siquiera se pasaba a menos que el componente estuviera envuelto en forwardRef.
¿Para qué sirve useImperativeHandle?
Reemplaza lo que el padre ve en ref.current. En lugar del nodo del DOM completo devuelves un objeto solo con los métodos que elijas, como focus y clear.
¿Los componentes de clase reciben ref como prop en React 19?
No. Una ref en un componente de clase sigue apuntando a la instancia del componente. El cambio aplica solo a los componentes de función.