Menu

React useId Hook: Eindeutige IDs für Labels und ARIA

useId gibt für jede Instanz einer Komponente eine eindeutige, stabile ID zurück, damit ein Label, ein Input und ein Hinweis per aria-describedby aufeinander zeigen können, auch wenn die Komponente viele Male vorkommt. Lerne, warum Math.random und Zähler beim Server-Rendering scheitern und warum useId nicht für Keys in Listen ist.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

useId gibt für jede Instanz einer Komponente eine eindeutige ID zurück, und bei jedem Render dieselbe. Nutze sie, um ein <label> mit seinem <input> zu verbinden oder ein Input über aria-describedby mit seinem Hinweis, wenn die Komponente mehr als einmal auf einer Seite vorkommen kann.

Dieselbe Komponente rendert zweimal und bekommt zwei verschiedene IDs, die unter jedem Feld stehen. Klicke auf den Text „Confirm password“, und der Fokus springt zum zweiten Input, weil sein htmlFor nur zur id dieses Inputs passt. Schreib id="password" fest anstelle von useId(), und beide Labels zeigen auf das erste Input.

Die Syntax

const id = useId();

useId nimmt keine Argumente und gibt einen String zurück. Ruf es wie jeden Hook auf der obersten Ebene der Komponente auf. Sein genaues Format ist intern und hat sich zwischen Versionen geändert: React 18 erzeugte :r1:, und React 19.2 erzeugt _r_1_ für eine Komponente, die zuerst im Browser gerendert wurde, und eine mit _R_ beginnende ID, aus der Position im Baum gebaut, für eine auf dem Server gerenderte. Parse sie nie und verlass dich nicht auf ihre Form.

Warum nicht Math.random oder ein Zähler

IDs für Barrierefreiheit müssen zwischen dem HTML, das der Server schickt, und dem Baum, den React im Browser baut, übereinstimmen. Die zwei naheliegenden Wege, eine ID zu erzeugen, bestehen diesen Test beide nicht.

// Changes on every render, and differs between server and browser
const id = 'field-' + Math.random().toString(36).slice(2);

// The server's counter keeps growing across requests, the browser starts at 0
let nextId = 0;
const id = 'field-' + nextId++;

Mit Server-Rendering (Next.js, Framework-Modus von React Router, jedes Setup mit hydrateRoot) schreibt der Server id="field-4817" ins HTML, der erste Render im Browser berechnet field-0, und React meldet eine Hydration-Abweichung. useId baut die ID aus der Position der Komponente im Baum, die auf beiden Seiten identisch ist.

Selbst ohne Server kann sich eine beim Rendern erzeugte ID bei jedem Render ändern. Dieses Beispiel zeigt den Unterschied ganz ohne Server:

Klicke ein paar Mal auf den Button. Der Wert von useId bleibt stehen, während die Zähler-ID mit jedem Render steigt, also zeigt alles, was auf die alte ID verwiesen hat (ein aria-describedby, ein Label), jetzt ins Leere. Den Zähler in useState(() => nextId++) zu packen würde das erneute Rendern beheben, aber nicht die Abweichung zum Server.

Mehrere IDs aus einem Aufruf

Eine Komponente mit mehreren Feldern braucht nicht mehrere Aufrufe von useId. Erzeuge eine Basis und hänge pro Element ein Suffix an.

Tippe ins E-Mail-Feld ein Wort ohne @: Der Fehler erscheint, und das aria-describedby des Inputs zeigt darauf, also liest ein Screenreader den Fehler vor, wenn das Input fokussiert ist. Rendere <SignupForm /> zweimal in App, und jede Kopie bekommt ihre eigene Basis-ID.

Nicht für Keys in Listen

Keys und IDs lösen verschiedene Probleme. Ein Key sagt React über Renders hinweg, welcher Eintrag welcher ist, also muss er aus den Daten kommen. useId gibt eine ID pro Instanz einer Komponente, und du kannst es nicht in map aufrufen.

// Wrong: breaks the rules of hooks, and the key is unrelated to the item
{todos.map((todo) => <Todo key={useId()} todo={todo} />)}

// Right: the key comes from the data
{todos.map((todo) => <Todo key={todo.id} todo={todo} />)}

Wenn deine Daten keine ID haben, erzeuge eine beim Anlegen des Eintrags (crypto.randomUUID() im Event-Handler, der ihn hinzufügt), nicht beim Rendern. Die Seite zu Listen und Keys erklärt, warum der Key bei seinem Eintrag bleiben muss.

Mehrere React-Roots auf einer Seite

Wenn zwei getrennte React-Apps auf derselben Seite rendern, könnten ihre IDs kollidieren. Gib jedem Root ein Präfix:

createRoot(document.getElementById('cart'), { identifierPrefix: 'cart-' });
createRoot(document.getElementById('chat'), { identifierPrefix: 'chat-' });

Beim Server-Rendering übergibst du dem Server-Renderer und hydrateRoot dasselbe identifierPrefix, damit beide Seiten dieselben IDs erzeugen.

Typische Fehler

Auf dem Server und im Browser einen anderen Baum rendern. useId hängt von der Position der Komponente ab, also kann ein Zweig wie typeof window === 'undefined' ? <A /> : <B /> über einem Feld die IDs zwischen den beiden Renders verschieben. Halte den Baum während der Hydration gleich und wechsle danach, in einem Effekt.

Das Element über seine ID suchen. document.getElementById(id) funktioniert, aber eine Ref ist der React-Weg, einen DOM-Knoten zu erreichen, und braucht überhaupt keine ID.

Sie als Zufallswert nutzen. Die ID ist innerhalb der App eindeutig, nicht zufällig, und aus dem Baum vorhersagbar. Nutze sie nicht für Sicherheitstokens, Cache-Keys oder irgendetwas, das über Sitzungen gespeichert wird.

Wann du ihn nutzt

Nutze useId, wann immer eine wiederverwendbare Komponente ein Attribut id braucht: Formularfelder aus einer Komponente, die oft verwendet wird, ein Tooltip, der per aria-describedby verbunden ist, ein Dialog mit aria-labelledby, Tabs mit aria-controls. Wenn eine ID nur ein Label mit seinem Input verbindet, kannst du die ID auch weglassen und das Input ins Label verschachteln (<label>Name <input /></label>); nutze useId, wenn sich die Elemente nicht verschachteln lassen.

Häufig gestellte Fragen

Was macht useId in React?

Es gibt einen String zurück, der für diese Instanz der Komponente eindeutig ist und bei jedem Render gleich bleibt. Du nutzt ihn, um Elemente per ID zu verbinden: htmlFor an einem Label, aria-describedby an einem Input, aria-labelledby an einem Dialog.

Warum nicht Math.random() oder einen Zähler für IDs nutzen?

Sie liefern auf dem Server und im Browser unterschiedliche Werte, also widersprechen sich eine serverseitig gerenderte Seite und ihre hydratisierte Version, und React meldet eine Hydration-Abweichung. Math.random() ändert sich außerdem bei jedem Render. useId leitet die ID aus der Position der Komponente im Baum ab, die an beiden Orten gleich ist.

Kann ich useId für Keys in einer Liste nutzen?

Nein. Ein Key muss aus deinen Daten kommen, damit React denselben Eintrag über Renders hinweg zuordnen kann. useId wird einmal pro Komponente aufgerufen, und es in map aufzurufen bricht ohnehin die Regeln der Hooks. Nutze die eigene ID des Eintrags.

Wie bekomme ich mehrere IDs aus einem useId-Aufruf?

Ruf useId einmal auf und hänge Suffixe an: ${id}-name, ${id}-email. Die Basis ist eindeutig, also sind es die Strings mit Suffix auch.

Kann ich die ID aus useId in einem CSS-Selektor nutzen?

Vermeide das. Die ID ist dafür gedacht, Elemente im DOM zu verbinden, und ihr genaues Format ist ein internes Detail, das sich zwischen React-Versionen geändert hat. Style mit einer Klasse und finde Elemente mit einer Ref statt mit querySelector.

Illustration der Programmiersprachen bei Coddy

Lerne mit Coddy zu programmieren

LOS GEHT'S