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

TypeScript signatur.ts
const id = useId();

Liefert einen String wie ":r0:", ":r1:" — eindeutig pro Komponenten-Instanz, stabil über Renders.

Klassisches Beispiel — Label-Input-Verknüpfung

TypeScript LabelInput.jsx
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:

TypeScript MehrereIDs.jsx
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. useId liefert 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.

TypeScript SSRSicher.jsx
// 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:" → identisch

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

TypeScript MultiRoot.jsx
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

/ Weiter

Zurück zu Hooks

Zur Übersicht