Cómo usar refs en Fragment en React 19.3
Usa las refs de Fragment de React 19.3 para gestionar eventos, foco, observadores y medidas en elementos hermanos sin un contenedor. Conoce sus límites y cuándo usar un elemento DOM.
En React 19.3, al pasar una ref a <Fragment> obtienes un FragmentInstance: un objeto cuyos métodos del DOM actúan sobre todos los hijos del Fragment como grupo, sin añadir ningún elemento a la página.
Seguramente alguna vez has envuelto un grupo de elementos hermanos en un <div> solo para poder asignarles una ref, y luego has visto cómo se rompía un grid de CSS o una fila flex porque las tarjetas habían dejado de ser hijos directos del contenedor. Las refs en Fragment te permiten asociar listeners, observers y gestión del foco a grupos de elementos hermanos sin modificar la estructura del DOM. Esta guía no repasa la versión completa (en novedades de React 19 se tratan las funcionalidades anteriores). Explica cómo funciona la API, a qué hijos llega cada método y cuándo sigue siendo mejor opción un elemento contenedor.
Puntos clave
- Las refs en Fragment son estables en React 19.3. La sintaxis abreviada
<></>no admite una ref, así que debes importarFragmenty escribir<Fragment ref={ref}>. - La ref contiene un
FragmentInstance, no un nodo del DOM ni un array, y expone doce métodos agrupados en cuatro categorías: eventos, foco, observers y medición/desplazamiento. - Los métodos de eventos, observers y medición solo alcanzan a los hijos del DOM de primer nivel del Fragment.
focus()yfocusLast()buscan en los elementos anidados en profundidad (depth-first). scrollIntoView()en unFragmentInstancesolo acepta el booleanoalignToTopy lanza un error si le pasas un objeto de opciones.
¿Cuál es el problema del div contenedor?
Antes de React 19.3, la única forma de actuar sobre un grupo de elementos hermanos era envolverlos en un elemento real del DOM y asignar la ref a ese elemento. El contenedor modifica el árbol de layout, y sistemas de maquetación como CSS grid y flexbox solo tienen en cuenta a los hijos directos.
// Before: the div is now the only grid item
<div className="grid">
<div ref={groupRef}><Card /><Card /><Card /></div>
</div>
// After: each card is still a grid item
<div className="grid">
<Fragment ref={groupRef}><Card /><Card /><Card /></Fragment>
</div>
En la versión con el div contenedor, las tarjetas pasan a ser hijas del contenedor, no del grid. La versión con Fragment deja el DOM exactamente igual que si no hubiera ninguna ref.
¿Cómo funciona una ref en un Fragment de React?
Una ref en un Fragment de React funciona como cualquier otra ref, salvo que el valor que asigna React es un FragmentInstance. Las refs en Fragment son una funcionalidad estable en la versión React 19.3, publicada el 9 de septiembre de 2026. Necesitas React 19.3 o posterior (react y react-dom 19.3.0+, incluido el parche 19.3.1), así que instala versiones 19.3.x coincidentes de ambos paquetes. Si vienes de una versión mayor anterior, consulta primero los errores comunes al actualizar a React 19.
La referencia de <Fragment> enumera doce métodos:
| Grupo de métodos | Métodos | Alcance | Notas |
|---|---|---|---|
| Eventos | addEventListener, removeEventListener, dispatchEvent | Hijos de primer nivel | dispatchEvent se dispara en el Fragment y puede propagarse (bubbling) hasta su padre en el DOM |
| Foco | focus, focusLast, blur | Anidados, en profundidad | focus() acepta FocusOptions |
| Observers | observeUsing, unobserveUsing | Hijos de primer nivel | IntersectionObserver o ResizeObserver; no se admiten nodos de texto |
| Medición/desplazamiento | getClientRects, getRootNode, compareDocumentPosition, scrollIntoView | Hijos de primer nivel | getRootNode funciona desde el padre; scrollIntoView solo acepta un booleano |
La mayoría de las sorpresas con esta API se deben a la regla del primer nivel. Un breve ejemplo anidado muestra exactamente a qué puede llegar cada grupo de métodos:
<Fragment ref={groupRef}>
<div className="row"> {/* first-level: gets listeners, observed, measured */}
<button>Save</button> {/* nested: not observed; focus() can reach it */}
</div>
<Card /> {/* component: the element Card renders at its top level is first-level */}
Some text {/* text node: observeUsing does not work on it */}
</Fragment>
Los componentes no cuentan como un nivel. Lo que importa es el elemento del DOM que cada componente renderiza en su nivel superior, por lo que <Card /> cuenta como hijo de primer nivel aunque sea un componente de React.
¿Cómo se añade un único event listener a todos los hijos?
Llamar a addEventListener en un FragmentInstance registra el manejador en todos los hijos del DOM de primer nivel a la vez. Regístralo en un efecto y elimínalo en la función de limpieza, pasando la misma referencia de función en ambos casos.
import { Fragment, useEffect, useRef } from 'react';
function ClickGroup({ onClick, children }) {
const groupRef = useRef(null);
useEffect(() => {
const group = groupRef.current;
if (!group) return;
group.addEventListener('click', onClick);
return () => group.removeEventListener('click', onClick);
}, [onClick]);
return <Fragment ref={groupRef}>{children}</Fragment>;
}
La referencia indica que React añade o elimina el listener a medida que los hijos entran o salen del Fragment. Pasa un onClick estable (por ejemplo, envuelto en useCallback), ya que una función nueva en cada render hace que el efecto cancele y vuelva a registrar la suscripción cada vez.
Los elementos anidados siguen activando el listener cuando el evento se propaga. Un clic en un <button> dentro de un <div> de primer nivel se propaga hacia arriba hasta ese <div>, donde está asociado el manejador. Los eventos que no se propagan, como focus o mouseenter, no llegarán al listener desde elementos más profundos.
¿Cómo se observa el grupo con observeUsing?
observeUsing() conecta un IntersectionObserver o un ResizeObserver que ya hayas creado a todos los hijos del DOM de primer nivel. unobserveUsing() desconecta esa misma instancia del observer de ellos.
import { Fragment, useEffect, useRef } from 'react';
function VisibilityGroup({ onVisibilityChange, children }) {
const groupRef = useRef(null);
useEffect(() => {
const group = groupRef.current;
if (!group) return;
const visible = new Set();
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) visible.add(entry.target);
else visible.delete(entry.target);
}
onVisibilityChange(visible.size > 0);
});
group.observeUsing(observer);
return () => {
group.unobserveUsing(observer);
observer.disconnect();
};
}, [onVisibilityChange]);
return <Fragment ref={groupRef}>{children}</Fragment>;
}
El Set registra qué hijos están intersectando en cada momento, de modo que el grupo se considera visible mientras alguna tarjeta esté en pantalla. Los hijos pueden ser componentes que nunca aceptan una ref. Como indica la referencia de Fragment, los nodos de texto no se observan, y React muestra una advertencia en desarrollo cuando un Fragment solo contiene texto.
Mover el foco con focus()
Llamar a focus() en una ref de Fragment mueve el foco al primer elemento enfocable del grupo. Esto funciona incluso cuando el input está dentro de un <label> o un <fieldset> renderizado por un componente hijo que no expone una ref, porque los métodos de foco buscan en los hijos anidados en profundidad.
import { Fragment, useEffect, useRef, useState } from 'react';
function EditAddress() {
const [editing, setEditing] = useState(false);
const fieldsRef = useRef(null);
useEffect(() => {
if (editing) fieldsRef.current?.focus();
}, [editing]);
return (
<section>
<button onClick={() => setEditing(true)}>Edit address</button>
{editing && (
<Fragment ref={fieldsRef}>
<AddressFields />
</Fragment>
)}
</section>
);
}
Una grabación de sesión (session replay) de un formulario o un modal puede mostrar que, tras un cambio de estado, el foco no cae en ningún sitio, de modo que la siguiente pulsación de tecla va a <body>. Llamar a focus() en una ref de Fragment lo soluciona sin necesidad de un contenedor. Usa focusLast() para apuntar al último elemento enfocable y pasa FocusOptions como { preventScroll: true } si lo necesitas. Un ref callback también sirve, pero mantenlo estable para que no se vuelva a ejecutar en cada render.
¿Cuándo sigue siendo preferible un elemento contenedor?
Usa un elemento contenedor cuando lo que necesitas es el propio nodo del DOM. Una ref de Fragment actúa sobre un grupo de hijos, pero no crea una caja. Mantén el contenedor cuando:
- Necesites aplicar estilos al grupo. Los fondos, bordes, padding y áreas de grid requieren un elemento real.
- Necesites una única bounding box.
getClientRects()devuelve un array plano de rectángulos de los hijos, no un único rectángulo combinado. - Tus objetivos estén anidados por debajo de los hijos de primer nivel. Los observers y los eventos que no se propagan nunca llegan a esos elementos.
Recuerda también que scrollIntoView() solo acepta alignToTop. Si necesitas desplazamiento suave o alineación con block, usa el método del elemento sobre un nodo real.
Conclusión
Las refs en Fragment te permiten asociar listeners, observers y foco a grupos de elementos hermanos sin romper layouts de grid o flex. Antes de usarlas, ten en cuenta la regla del primer nivel: los eventos, los observers y la medición se detienen en los hijos directos del DOM, mientras que el foco busca más en profundidad. Para probarlo, actualiza a React 19.3 y sustituye un div contenedor que solo sirva para la ref por <Fragment ref>. Esta misma versión también añadió compatibilidad con Trusted Types, descrita en el anuncio de la versión React 19.3.
Preguntas frecuentes
¿Cómo se tipa una ref de Fragment en TypeScript?
Usa useRef con el tipo FragmentInstance exportado desde react y un valor inicial de null. En @types/react 19.3, la interfaz FragmentInstance está vacía por defecto. @types/react-dom la amplía mediante declare module 'react' para añadir focus, observeUsing, addEventListener y el resto de métodos. Si parece que faltan esos métodos, instala @types/react-dom y asegúrate de que el módulo react-dom forma parte del grafo de tipos de tu proyecto.
¿A qué hijo se desplaza scrollIntoView en una ref de Fragment?
Si alignToTop es true o se omite, scrollIntoView() alinea el primer hijo del Fragment con la parte superior del ancestro desplazable. Si alignToTop es false, alinea el último hijo con la parte inferior. Un Fragment vacío recurre al hermano más cercano o al padre. Pasar un objeto de opciones, como uno con behavior establecido en 'smooth', lanza un error en lugar de desplazar la vista.
¿Funcionan los event listeners de una ref de Fragment dentro de un límite Activity oculto?
No. React no aplica los listeners añadidos mediante el addEventListener de un FragmentInstance a los hijos que están dentro de un límite Activity oculto. Cuando el límite pasa de oculto a visible, React aplica esos listeners automáticamente. Tu código no tiene que volver a registrarlos cuando el contenido se hace visible.
¿Qué hace blur() en una ref de Fragment si el foco está fuera del grupo?
Nada. blur() en un FragmentInstance quita el foco de document.activeElement solo cuando ese elemento está dentro del Fragment. Si el elemento activo está en cualquier otra parte de la página, la llamada no hace nada. Eso significa que puedes llamar a blur() en un manejador de cierre o cancelación sin comprobar antes dónde está el foco.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k