12k
All articles

So verwenden Sie Fragment-Refs in React 19.3

Nutzen Sie Fragment-Refs in React 19.3 für Ereignisse, Fokus, Observer und Messungen bei Geschwisterelementen ohne Wrapper. Erfahren Sie mehr über Grenzen und passende DOM-Elemente.

OpenReplay Team
OpenReplay Team
So verwenden Sie Fragment-Refs in React 19.3

Wenn Sie in React 19.3 einen Ref an <Fragment> übergeben, erhalten Sie eine FragmentInstance. Das ist ein Objekt, dessen DOM-Methoden auf alle Kinder des Fragments gemeinsam wirken, ohne dass der Seite ein zusätzliches Element hinzugefügt wird.

Vermutlich haben Sie schon einmal eine Gruppe von Geschwisterelementen in ein <div> gepackt, nur um einen Ref darauf setzen zu können. Anschließend ist dann ein CSS-Grid oder eine Flex-Zeile auseinandergefallen, weil die Cards keine direkten Kinder des Containers mehr waren. Mit Fragment-Refs können Sie Listener, Observer und Fokussteuerung an Gruppen von Geschwisterelementen anbinden, ohne die DOM-Struktur zu verändern. Dieser Leitfaden verzichtet auf eine vollständige Zusammenfassung des Releases (Neuerungen in React 19 behandelt die früheren Features). Er erklärt, wie die API funktioniert, welche Kinder die einzelnen Methoden erreichen und wann ein Wrapper-Element nach wie vor die bessere Wahl ist.

Das Wichtigste in Kürze

  • Fragment-Refs sind in React 19.3 stabil. Die Kurzschreibweise <></> kann keinen Ref aufnehmen. Sie müssen daher Fragment importieren und <Fragment ref={ref}> schreiben.
  • Der Ref enthält eine FragmentInstance. Das ist weder ein DOM-Knoten noch ein Array. Die Instanz stellt zwölf Methoden in vier Gruppen bereit: Events, Fokus, Observer sowie Messung/Scrollen.
  • Event-, Observer- und Messmethoden erreichen nur die DOM-Kinder der ersten Ebene des Fragments. focus() und focusLast() durchsuchen verschachtelte Elemente per Tiefensuche.
  • scrollIntoView() akzeptiert auf einer FragmentInstance nur den booleschen Wert alignToTop und wirft einen Fehler, wenn Sie ein Options-Objekt übergeben.

Was ist das Wrapper-Div-Problem?

Vor React 19.3 gab es nur einen Weg, auf eine Gruppe von Geschwisterelementen zuzugreifen: Man musste sie in ein echtes DOM-Element einschließen und den Ref auf dieses Element setzen. Der Wrapper verändert jedoch den Layout-Baum, und Layout-Systeme wie CSS-Grid und Flexbox berücksichtigen nur direkte Kinder.

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

In der Variante mit Wrapper-Div werden die Cards zu Kindern des Wrappers statt des Grids. Die Fragment-Variante lässt das DOM genau so, wie es auch ganz ohne Ref aussehen würde.

Wie funktioniert ein React-Fragment-Ref?

Ein React-Fragment-Ref funktioniert wie jeder andere Ref. Der einzige Unterschied besteht darin, dass React ihm eine FragmentInstance als Wert zuweist. Fragment-Refs sind ein stabiles Feature des Releases React 19.3, das am 9. September 2026 veröffentlicht wurde. Sie benötigen React 19.3 oder neuer (react und react-dom ab 19.3.0, einschließlich des Patches 19.3.1). Installieren Sie also für beide Pakete passende 19.3.x-Versionen. Wenn Sie von einer älteren Major-Version umsteigen, lesen Sie zuerst Häufige Fehler beim Upgrade auf React 19.

Die <Fragment>-Referenz führt zwölf Methoden auf:

MethodengruppeMethodenReichweiteHinweise
EventsaddEventListener, removeEventListener, dispatchEventKinder der ersten EbenedispatchEvent löst das Event auf dem Fragment aus. Es kann zum DOM-Elternelement aufsteigen (Bubbling).
Fokusfocus, focusLast, blurVerschachtelt, per Tiefensuchefocus() akzeptiert FocusOptions
ObserverobserveUsing, unobserveUsingKinder der ersten EbeneIntersectionObserver oder ResizeObserver. Textknoten werden nicht unterstützt.
Messen/ScrollengetClientRects, getRootNode, compareDocumentPosition, scrollIntoViewKinder der ersten EbenegetRootNode arbeitet ausgehend vom Elternelement. scrollIntoView akzeptiert nur einen booleschen Wert.

Die meisten Überraschungen mit dieser API gehen auf die Regel der ersten Ebene zurück. Ein kurzes verschachteltes Beispiel zeigt genau, was die einzelnen Methodengruppen erreichen:

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

Komponenten zählen nicht als eigene Ebene. Entscheidend ist das DOM-Element, das eine Komponente auf ihrer obersten Ebene rendert. Deshalb gilt <Card /> als Element der ersten Ebene, obwohl es sich um eine React-Komponente handelt.

Wie fügen Sie allen Kindern einen gemeinsamen Event-Listener hinzu?

Ein Aufruf von addEventListener auf einer FragmentInstance registriert den Handler gleichzeitig auf allen DOM-Kindern der ersten Ebene. Registrieren Sie ihn in einem Effect und entfernen Sie ihn im Cleanup. Übergeben Sie dabei in beiden Fällen dieselbe Funktionsreferenz.

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

Laut Referenz fügt React den Listener automatisch hinzu oder entfernt ihn, wenn Kinder in das Fragment aufgenommen werden oder es verlassen. Übergeben Sie ein stabiles onClick (zum Beispiel mit useCallback umschlossen). Eine neue Funktion bei jedem Render führt dazu, dass der Effect sich jedes Mal ab- und wieder anmeldet.

Verschachtelte Elemente lösen den Listener trotzdem aus, wenn das Event aufsteigt. Ein Klick auf einen <button> innerhalb eines <div> der ersten Ebene steigt bis zu diesem <div> auf, an dem der Handler hängt. Events ohne Bubbling, etwa focus oder mouseenter, erreichen den Listener von tiefer liegenden Elementen aus nicht.

Wie überwachen Sie die Gruppe mit observeUsing?

observeUsing() verbindet einen bereits erstellten IntersectionObserver oder ResizeObserver mit allen DOM-Kindern der ersten Ebene. unobserveUsing() trennt genau diese Observer-Instanz wieder von ihnen.

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

Das Set merkt sich, welche Kinder sich gerade im sichtbaren Bereich befinden. Die Gruppe gilt also als sichtbar, solange mindestens eine Card auf dem Bildschirm zu sehen ist. Die Kinder dürfen auch Komponenten sein, die selbst keinen Ref annehmen. Wie die Fragment-Referenz anmerkt, werden Textknoten nicht beobachtet. Enthält ein Fragment ausschließlich Text, gibt React im Entwicklungsmodus eine Warnung aus.

Fokus mit focus() verschieben

Ein Aufruf von focus() auf einem Fragment-Ref verschiebt den Fokus auf das erste fokussierbare Element der Gruppe. Das funktioniert auch dann, wenn das Eingabefeld in einem <label> oder <fieldset> liegt, das von einer Kindkomponente ohne eigenen Ref gerendert wird. Die Fokusmethoden durchsuchen verschachtelte Kinder nämlich per Tiefensuche.

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

Ein Session-Replay eines Formulars oder Modals kann zeigen, dass der Fokus nach einer Zustandsänderung ins Leere geht und der nächste Tastendruck deshalb bei <body> landet. Mit focus() auf einem Fragment-Ref beheben Sie das ohne Wrapper. Mit focusLast() steuern Sie das letzte fokussierbare Element an. Bei Bedarf übergeben Sie FocusOptions wie { preventScroll: true }. Ein Ref-Callback funktioniert ebenfalls. Halten Sie ihn jedoch stabil, damit er nicht bei jedem Render erneut ausgeführt wird.

Wann ist ein Wrapper-Element weiterhin sinnvoll?

Verwenden Sie ein Wrapper-Element, wenn Sie den DOM-Knoten selbst benötigen. Ein Fragment-Ref wirkt auf eine Gruppe von Kindern, erzeugt aber keine eigene Box. Behalten Sie den Wrapper in folgenden Fällen bei:

  • Sie möchten die Gruppe stylen. Hintergründe, Rahmen, Padding und Grid-Areas erfordern ein echtes Element.
  • Sie benötigen eine einzige Bounding Box. getClientRects() liefert ein flaches Array mit den Rechtecken der einzelnen Kinder, kein kombiniertes Gesamtrechteck.
  • Ihre Zielelemente liegen tiefer als die erste Ebene. Observer und Events ohne Bubbling erreichen diese Elemente nie.

Denken Sie außerdem daran, dass scrollIntoView() nur alignToTop akzeptiert. Wenn Sie Smooth Scrolling oder eine block-Ausrichtung benötigen, verwenden Sie die Element-Methode auf einem echten Knoten.

Fazit

Mit Fragment-Refs können Sie Listener, Observer und Fokus an Gruppen von Geschwisterelementen anbinden, ohne Grid- oder Flex-Layouts zu beschädigen. Prüfen Sie vor dem Einsatz die Regel der ersten Ebene: Events, Observer und Messungen enden bei den direkten DOM-Kindern, während die Fokussuche tiefer reicht. Zum Ausprobieren aktualisieren Sie auf React 19.3 und ersetzen ein Wrapper-Div, das nur für einen Ref existiert, durch <Fragment ref>. Dasselbe Release hat außerdem Unterstützung für Trusted Types eingeführt, die im Release-Beitrag zu React 19.3 beschrieben wird.

FAQs

Wie typisiere ich einen Fragment-Ref in TypeScript?

Verwenden Sie useRef mit dem aus react exportierten Typ FragmentInstance und dem Anfangswert null. In @types/react 19.3 ist das Interface FragmentInstance standardmäßig leer. @types/react-dom erweitert es per declare module 'react' um focus, observeUsing, addEventListener und die übrigen Methoden. Wenn diese Methoden zu fehlen scheinen, installieren Sie @types/react-dom und stellen Sie sicher, dass das Modul react-dom Teil des Typgraphen Ihres Projekts ist.

Zu welchem Kind scrollt scrollIntoView bei einem Fragment-Ref?

Ist alignToTop true oder wird es weggelassen, richtet scrollIntoView() das erste Kind des Fragments am oberen Rand des scrollbaren Vorfahren aus. Ist alignToTop false, wird das letzte Kind am unteren Rand ausgerichtet. Bei einem leeren Fragment wird ersatzweise das nächstgelegene Geschwisterelement oder das Elternelement verwendet. Die Übergabe eines Options-Objekts, etwa mit behavior auf 'smooth', führt zu einem Fehler, statt zu scrollen.

Funktionieren Event-Listener eines Fragment-Refs innerhalb einer verborgenen Activity-Boundary?

Nein. React wendet Listener, die über addEventListener einer FragmentInstance hinzugefügt wurden, nicht auf Kinder innerhalb einer verborgenen Activity-Boundary an. Wechselt die Boundary von verborgen zu sichtbar, wendet React diese Listener automatisch an. Ihr eigener Code muss sie also nicht erneut registrieren, wenn der Inhalt sichtbar wird.

Was bewirkt blur() auf einem Fragment-Ref, wenn der Fokus außerhalb der Gruppe liegt?

Nichts. blur() auf einer FragmentInstance entfernt den Fokus von document.activeElement nur, wenn sich dieses Element innerhalb des Fragments befindet. Liegt das aktive Element an einer anderen Stelle der Seite, bleibt der Aufruf wirkungslos. Sie können blur() daher in einem Schließen- oder Abbrechen-Handler aufrufen, ohne vorher zu prüfen, wo der Fokus liegt.

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.