useTransition & useDeferredValue
Wichtiges sofort, Aufwendiges gleich danach
Mit beiden sagst du React: „Das hier ist wichtig, das andere darf warten.“ Die Eingabe bleibt flüssig, während die schwere Liste hinterherkommt.
Stell dir vor, du tippst in ein Suchfeld – und bei jedem Buchstaben zeichnet React eine Liste mit 5.000 Einträgen neu. Das Feld hakt, weil React beide Aufgaben für gleich wichtig hält.
Mit diesen beiden Hooks sagst du: „Der Buchstabe muss sofort erscheinen. Die Liste darf ein paar Millisekunden später kommen.“ Das nennt React einen Übergang (englisch: transition).
// Variante A: useTransition – du steuerst das UPDATE
const [istAmLaufen, starteUebergang] = useTransition();
function tippen(e) {
setQuery(e.target.value); // dringend: das Feld
starteUebergang(() => {
setErgebnisse(suche(e.target.value)); // darf warten
});
}
// Variante B: useDeferredValue – du steuerst den WERT
const [query, setQuery] = useState("");
const langsamerQuery = useDeferredValue(query);
// query -> ins Eingabefeld (immer aktuell)
// langsamerQuery -> in die teure Liste (hinkt kurz hinterher)
const ergebnisse = useMemo(() => suche(langsamerQuery), [langsamerQuery]); Ein Kellner nimmt erst alle Bestellungen auf (schnell, sichtbar) und geht dann in die Küche (langsam). Ohne Transition würde er nach jeder einzelnen Bestellung in die Küche laufen – und der nächste Gast wartet.
Welches von beiden?
useTransition ... wenn du den setState-Aufruf selbst in der Hand hast
(Tabwechsel, Filter setzen, Navigation)
Bonus: istAmLaufen -> Ladeanzeige möglich
useDeferredValue ... wenn der Wert von außen kommt (Prop) oder du
nur eine "hinterherhinkende" Kopie brauchst
Kein Zugriff auf den Setter nötig function Tabs() {
const [tab, setTab] = useState("start");
const [istAmLaufen, starteUebergang] = useTransition();
function wechsle(neu: string) {
starteUebergang(() => setTab(neu)); // Klick fühlt sich sofort an
}
return (
<>
<button onClick={() => wechsle("berichte")}>Berichte</button>
<div style={{ opacity: istAmLaufen ? 0.6 : 1 }}>
{tab === "berichte" ? <TeurerBericht /> : <Start />}
</div>
</>
);
} - +
Große Listen filtern/sortieren während der Eingabe
- +
Tabs oder Ansichten wechseln, die viel rendern
- +
Ladeanzeige zeigen, ohne die alte Ansicht wegzuwerfen
- −
Eingabefelder selbst – der getippte Wert muss sofort erscheinen
- −
Als Ersatz für Debouncing bei Netzwerkanfragen
- −
Wenn nichts spürbar ruckelt: erst messen, dann optimieren
starteUebergang(() => setQuery(e.target.value)) macht genau das kaputt, was du reparieren willst: Der Text im Feld erscheint dann verzögert. Dringende Updates bleiben draußen.
Transitions verhindern Ruckeln beim Rendern. Sie verhindern keine Netzwerkanfragen. Wenn jeder Tastendruck eine API aufrufen würde, brauchst du weiterhin Debounce oder eine Query-Bibliothek – gern zusätzlich zur Transition.
▸ Genauer erklärt: was heißt „darf warten“ technisch? optional
React arbeitet ein Update normalerweise am Stück ab – währenddessen reagiert die Seite nicht. Ein Übergang darf React dagegen unterbrechen: Kommt zwischendrin etwas Dringendes (ein Tastendruck), macht React zuerst das, und nimmt den Übergang danach wieder auf.
Es ist also keine Verzögerung wie bei setTimeout, sondern eine Rangfolge. Deshalb hilft es auch nicht gegen zu viele Netzwerkanfragen – dafür brauchst du weiterhin DebounceErst handeln, wenn kurz Ruhe war – zum Beispiel erst suchen, wenn 300 ms nicht getippt wurde.→ oder eine Query-Bibliothek.
startTransition(async () => { await speichern(); setStatus("fertig"); }) hält isPending über das ganze await hinweg auf true. Darauf bauen die neuen Formular-Actions auf.
Sitzt das schon?
4 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.
Jetzt selbst schreiben
Die Liste hat 3.000 Einträge und wird bei jedem Tastendruck neu gefiltert. Sorge dafür, dass das Eingabefeld sofort reagiert und die Liste nachzieht – mit useDeferredValue.
- □Der Text im Eingabefeld erscheint sofort (
query) - □Die Liste filtert nach dem verzögerten Wert
- □Während die Liste hinterherhinkt, wird sie leicht abgeblendet
import { useDeferredValue, useMemo, useState } from "react"; const DATEN = Array.from({ length: 3000 }, (_, i) => "Eintrag " + i); export default function App() { const [query, setQuery] = useState(""); // TODO: verzögerten Wert ableiten const treffer = useMemo( () => DATEN.filter((d) => d.includes(query)).slice(0, 20), [query] ); return ( <div style={{ fontFamily: "system-ui", padding: 16 }}> <input aria-label="suche" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="z. B. 123" /> <ul data-testid="liste"> {treffer.map((t) => ( <li key={t}>{t}</li> ))} </ul> </div> ); }