useId (React 18+) erzeugt einen eindeutigen, stabilen String pro Komponenten-Instanz. Hauptzweck: Accessibility-Attribute wie aria-describedby, aria-labelledby oder htmlFor/id-Verknüpfungen zwischen <label> und <input>. Der Hook ist SSR-kompatibel: die ID, die der Server rendert, ist dieselbe, die der Client beim Hydration nutzt — kein Hydration-Mismatch. useId ist nicht für List-Keys gedacht — dafür gibt's stabile Daten-IDs.
Signatur
const id = useId();Liefert einen String wie ":r0:", ":r1:" — eindeutig pro Komponenten-Instanz, stabil über Renders.
Klassisches Beispiel — Label-Input-Verknüpfung
import { useId } from 'react';
export default function PasswordField() {
const id = useId();
return (
<>
<label htmlFor={id}>Passwort</label>
<input id={id} type="password" />
</>
);
}Mehrere <PasswordField />-Instanzen auf derselben Seite bekommen jeweils ihre eigene ID — keine Kollision, kein hartcodiertes "password-field".
Mehrere zusammenhängende IDs aus einem useId
Wer mehrere IDs in einer Komponente braucht (z.B. Input + Helper-Text + Error), nutzt einen useId und hängt Suffixe an:
export default function EmailField({ error, hint }) {
const id = useId();
return (
<>
<label htmlFor={`${id}-input`}>E-Mail</label>
<input
id={`${id}-input`}
type="email"
aria-describedby={`${id}-hint ${id}-error`}
aria-invalid={!!error}
/>
<p id={`${id}-hint`}>Format: name@domain.de</p>
{error && <p id={`${id}-error`}>{error}</p>}
</>
);
}Pro Komponenten-Instanz gibt's ein gemeinsames Präfix — Input, Hint, Error sind a11y-mäßig verknüpft.
Was useId NICHT ist
- Kein Key für Listen.
useIdliefert dieselbe ID pro Komponenten-Instanz. In einer Liste hätten alle Items denselben Hook-Wert — bricht das key-Konzept. Für List-Keys: stabile Daten-IDs (z.B.crypto.randomUUID()beim Erstellen). - Kein UUID-Generator. Die zurückgegebenen IDs sind interne React-Identifier wie
:r0:— semantisch und visuell nicht für End-User-sichtbare Strings gedacht. - Kein State-Identifier. Wer ein „eindeutiger Session-Wert pro Komponente" braucht:
useState(() => crypto.randomUUID()).
SSR-Kompatibilität
Vor useId war eindeutige ID-Generierung mit SSR ein klassisches Problem: Math.random() lieferte unterschiedliche Werte auf Server und Client — Hydration-Mismatch-Warning. Module-Level-Counter funktionierten nur im Single-Render, brachen bei nebenläufigem SSR (Streaming).
useId löst das, indem React intern eine deterministische Reihenfolge basierend auf der Komponenten-Position im Tree nutzt. Server und Client kommen bei demselben Tree zur gleichen ID.
// FRÜHER: Hydration-Mismatch im SSR
const id = useState(() => Math.random().toString(36).slice(2))[0];
// Server: "abc123", Client: "def456" → Mismatch
// HEUTE: SSR-sicher
const id = useId();
// Server: ":r0:", Client: ":r0:" → identischMulti-Root: identifierPrefix
Wenn eine Seite mehrere unabhängige React-Trees enthält (z.B. mehrere hydrateRoot-Calls), kann es zu Kollisionen kommen. Lösung: identifierPrefix an hydrateRoot übergeben.
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('app-1'), <App1 />, {
identifierPrefix: 'app1-',
});
hydrateRoot(document.getElementById('app-2'), <App2 />, {
identifierPrefix: 'app2-',
});useId in App1 liefert dann z.B. :app1-r0:, in App2 :app2-r0: — garantiert kollisionsfrei.
Besonderheiten
useId liefert eine stabile ID pro Komponenten-Instanz — Reference-Identity.
Bei Re-Renders bleibt die ID gleich. Bei Unmount + Remount kann React eine neue ID vergeben. Bei zwei verschiedenen Instanzen derselben Komponente: zwei verschiedene IDs.
ID-Format ist React-intern — :r0:, `:r1:` usw.
Die genaue Form ist nicht spezifiziert. Doppelpunkte sind in CSS-Selektoren erlaubt, aber als CSS-Klassennamen problematisch. useId-IDs sind für HTML-Attribute (id, for, aria-*) gedacht, nicht für CSS-Klassen.
Hauptanwendung: Accessibility-Verknüpfungen.
htmlFor/id zwischen Label und Input, aria-describedby auf Hint-Text, aria-labelledby auf Custom-Komponenten. Ohne useId müsste man hartcodierte IDs vermeiden oder mit globalen Countern arbeiten — beides fehleranfällig.
NICHT für List-Keys.
arr.map(() => <Item key={useId()} />) ist Hook-Verletzung (Hooks in Schleifen!) UND falsch (Items brauchen daten-stabile Keys, nicht Komponenten-stabile).
Suffixe für mehrere IDs in einer Komponente.
Einen useId, mehrere Suffixe (${id}-name, ${id}-error). Spart Hook-Aufrufe und semantisch klarer als mehrere useId-Aufrufe.
SSR-sicher seit React 18 — Hydration-Mismatch ade.
Pre-18 war eindeutige ID + SSR ein Albtraum. useId löst das durch deterministische Tree-Position-Codierung. Server und Client kommen garantiert zum selben Wert für dieselbe Komponenten-Position.
identifierPrefix für Multi-Root-Apps.
Wenn mehrere React-Roots auf derselben Seite hydrated werden, kann es zu ID-Kollisionen kommen. hydrateRoot(el, jsx, { identifierPrefix: 'app-' }) löst das.
useId hat keine Argumente und keine Optionen.
Im Gegensatz zu useState (Initial-Wert), useEffect (Callback, deps), useMemo (Compute, deps) ist useId einfach useId() — kein Parameter. Die ID kommt aus dem React-Internals, nicht aus User-Input.
Weiterführende Ressourcen
Externe Quellen
- useId – react.dev
- hydrateRoot – react.dev
- WAI-ARIA: aria-describedby – MDN
- Labels and Inputs – web.dev