12k
All articles

Como usar Fragment Refs no React 19.3

Use refs de Fragment no React 19.3 para lidar com eventos, foco, observadores e medidas em elementos irmãos sem um wrapper. Veja os limites e quando manter um elemento DOM.

OpenReplay Team
OpenReplay Team
Como usar Fragment Refs no React 19.3

No React 19.3, passar uma ref para <Fragment> retorna um FragmentInstance: um objeto cujos métodos de DOM atuam sobre todos os filhos do Fragment como um grupo, sem adicionar nenhum elemento à página.

Você provavelmente já envolveu um grupo de elementos irmãos em uma <div> só para conseguir colocar uma ref neles e, em seguida, viu um grid CSS ou uma linha flex quebrar porque os cards deixaram de ser filhos diretos do contêiner. As Fragment refs permitem anexar listeners, observers e controle de foco a grupos de irmãos sem alterar a estrutura do DOM. Este guia não faz um resumo completo da versão (o artigo o que há de novo no React 19 aborda os recursos anteriores). Ele explica como a API funciona, quais filhos cada método consegue alcançar e quando um elemento wrapper ainda é a melhor escolha.

Principais pontos

  • As Fragment refs são estáveis no React 19.3. A forma abreviada <></> não aceita ref, então você precisa importar Fragment e escrever <Fragment ref={ref}>.
  • A ref armazena um FragmentInstance, que não é um nó do DOM nem um array, e expõe doze métodos divididos em quatro grupos: eventos, foco, observers e medição/rolagem.
  • Os métodos de eventos, observers e medição alcançam apenas os filhos de DOM de primeiro nível do Fragment. Já focus() e focusLast() percorrem os elementos aninhados em profundidade (depth-first).
  • scrollIntoView() em um FragmentInstance aceita apenas o booleano alignToTop e lança um erro se você passar um objeto de opções.

Qual é o problema da div wrapper?

Antes do React 19.3, a única forma de atuar sobre um grupo de elementos irmãos era envolvê-los em um elemento real do DOM e colocar a ref nesse elemento. O wrapper altera a árvore de layout, e sistemas de layout como CSS grid e flexbox enxergam apenas os filhos diretos.

// 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>

Na versão com a div wrapper, os cards passam a ser filhos do wrapper, e não do grid. A versão com Fragment deixa o DOM exatamente como ele seria sem ref nenhuma.

Como funciona uma Fragment ref no React?

Uma Fragment ref no React funciona como qualquer outra ref, com a diferença de que o valor atribuído pelo React é um FragmentInstance. As Fragment Refs são um recurso estável na versão 19.3 do React, publicada em 9 de setembro de 2026. Você precisa do React 19.3 ou superior (react e react-dom 19.3.0+, incluindo o patch 19.3.1), portanto instale versões 19.3.x correspondentes dos dois pacotes. Se você estiver vindo de uma versão major mais antiga, confira antes os erros comuns ao atualizar para o React 19.

A referência de <Fragment> lista doze métodos:

Grupo de métodosMétodosAlcanceObservações
EventosaddEventListener, removeEventListener, dispatchEventFilhos de primeiro níveldispatchEvent dispara no Fragment e pode propagar (bubble) até o pai no DOM
Focofocus, focusLast, blurAninhados, em profundidadefocus() aceita FocusOptions
ObserversobserveUsing, unobserveUsingFilhos de primeiro nívelIntersectionObserver ou ResizeObserver; nós de texto não são suportados
Medição/rolagemgetClientRects, getRootNode, compareDocumentPosition, scrollIntoViewFilhos de primeiro nívelgetRootNode atua a partir do pai; scrollIntoView aceita apenas um booleano

A maioria das surpresas com essa API vem da regra do primeiro nível. Um pequeno exemplo aninhado mostra exatamente o que cada grupo de métodos consegue alcançar:

<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>

Componentes não contam como um nível. O que importa é o elemento de DOM que cada componente renderiza no seu nível mais alto, então <Card /> conta como primeiro nível, mesmo sendo um componente React.

Como adicionar um único event listener a todos os filhos?

Chamar addEventListener em um FragmentInstance registra o handler em todos os filhos de DOM de primeiro nível de uma só vez. Registre-o em um effect e remova-o na função de cleanup, passando a mesma referência de função nas duas chamadas.

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>;
}

A referência informa que o React adiciona ou remove o listener à medida que os filhos entram ou saem do Fragment. Passe um onClick estável (por exemplo, envolvido em useCallback), porque uma nova função a cada renderização faz o effect cancelar e refazer a inscrição toda vez.

Elementos aninhados ainda acionam o listener quando o evento se propaga. Um clique em um <button> dentro de uma <div> de primeiro nível se propaga até essa <div>, onde o handler está anexado. Eventos que não se propagam, como focus ou mouseenter, não chegam ao listener a partir de elementos mais profundos.

Como monitorar o grupo com observeUsing?

observeUsing() conecta um IntersectionObserver ou ResizeObserver já criado a todos os filhos de DOM de primeiro nível. unobserveUsing() desconecta essa mesma instância de observer desses filhos.

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>;
}

O Set registra quais filhos estão atualmente em interseção, de modo que o grupo é considerado visível enquanto qualquer card estiver na tela. Os filhos podem ser componentes que nunca aceitam ref. Como observa a referência de Fragment, nós de texto não são observados, e o React exibe um aviso em desenvolvimento quando um Fragment contém apenas texto.

Mova o foco com focus()

Chamar focus() em uma Fragment ref move o foco para o primeiro elemento focável do grupo. Isso funciona mesmo quando o input está dentro de um <label> ou <fieldset> renderizado por um componente filho que não expõe uma ref, porque os métodos de foco percorrem os filhos aninhados em profundidade.

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>
  );
}

Um session replay de um formulário ou modal pode mostrar o foco se perdendo após uma mudança de estado, fazendo com que a próxima tecla digitada vá para o <body>. Chamar focus() em uma Fragment ref resolve isso sem precisar de um wrapper. Use focusLast() para mirar o último elemento focável e passe FocusOptions como { preventScroll: true }, se necessário. Um ref callback também funciona, mas mantenha-o estável para que ele não seja executado novamente a cada renderização.

Quando você ainda precisa de um elemento wrapper?

Use um elemento wrapper quando o que você precisa é do próprio nó do DOM. Uma Fragment ref atua sobre um grupo de filhos; ela não cria uma caixa. Mantenha o wrapper quando:

  • Você precisa estilizar o grupo. Backgrounds, bordas, padding e grid areas exigem um elemento real.
  • Você precisa de uma única bounding box. getClientRects() retorna um array plano com os retângulos dos filhos, e não um único retângulo combinado.
  • Seus alvos estão aninhados abaixo dos filhos de primeiro nível. Observers e eventos que não se propagam nunca alcançam esses elementos.

Lembre-se também de que scrollIntoView() aceita apenas alignToTop. Se você precisar de rolagem suave ou de alinhamento com block, use o método do elemento em um nó real.

Conclusão

As Fragment refs permitem anexar listeners, observers e foco a grupos de irmãos sem quebrar layouts em grid ou flex. Antes de usar uma, verifique a regra do primeiro nível: eventos, observers e medição param nos filhos diretos do DOM, enquanto o foco busca em níveis mais profundos. Para experimentar, atualize para o React 19.3 e substitua uma div wrapper que existe apenas para receber a ref por <Fragment ref>. A mesma versão também adicionou suporte a Trusted Types, descrito no post de lançamento do React 19.3.

Perguntas frequentes

Como tipar uma Fragment ref em TypeScript?

Use useRef com o tipo FragmentInstance exportado de react e um valor inicial null. No @types/react 19.3, a interface FragmentInstance é vazia por padrão. O @types/react-dom a estende por meio de declare module 'react' para adicionar focus, observeUsing, addEventListener e os demais métodos. Se esses métodos parecerem estar faltando, instale o @types/react-dom e verifique se o módulo react-dom faz parte do grafo de tipos do seu projeto.

Para qual filho o scrollIntoView rola em uma Fragment ref?

Se alignToTop for true ou for omitido, scrollIntoView() alinha o primeiro filho do Fragment com o topo do ancestral rolável. Se alignToTop for false, ele alinha o último filho com a parte inferior. Um Fragment vazio recorre ao irmão mais próximo ou ao pai. Passar um objeto de opções, como um com behavior definido como 'smooth', lança um erro em vez de rolar.

Os event listeners de uma Fragment ref funcionam dentro de um Activity boundary oculto?

Não. O React não aplica listeners adicionados via addEventListener de um FragmentInstance a filhos dentro de um Activity boundary oculto. Quando o boundary passa de oculto para visível, o React aplica esses listeners automaticamente. Seu código não precisa registrá-los novamente depois que o conteúdo se torna visível.

O que blur() faz em uma Fragment ref se o foco estiver fora do grupo?

Nada. blur() em um FragmentInstance remove o foco de document.activeElement somente quando esse elemento está dentro do Fragment. Se o elemento ativo estiver em qualquer outro lugar da página, a chamada não faz nada. Isso significa que você pode chamar blur() em um handler de fechar ou cancelar sem precisar verificar antes onde está o foco.

DevTools for the frontend

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

We use cookies to improve your experience. By using our site, you accept cookies.