Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Zur Übersicht
Fortgeschritten 11 Min Lesezeit

useTransition & useDeferredValue

Wichtiges sofort, Aufwendiges gleich danach

In einem Satz

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

beide.tsx
// 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]);
Bildlich gesprochen

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?

auswahl.txt
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
tabs.tsx
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>
    </>
  );
}
Ohne Transition friert der Klick ein, bis der teure Tab fertig gerendert ist.
Nimm es, wenn …
  • +

    Große Listen filtern/sortieren während der Eingabe

  • +

    Tabs oder Ansichten wechseln, die viel rendern

  • +

    Ladeanzeige zeigen, ohne die alte Ansicht wegzuwerfen

Lass es, wenn …
  • Eingabefelder selbst – der getippte Wert muss sofort erscheinen

  • Als Ersatz für Debouncing bei Netzwerkanfragen

  • Wenn nichts spürbar ruckelt: erst messen, dann optimieren

Stolperstein Den Eingabewert nie in die Transition packen

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.

Gut zu wissen Es ersetzt kein Debouncing

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.

Gut zu wissen React 19: Übergänge dürfen warten

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

Flüssige Suche über eine große Liste

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

Hirefullstack

Ihr braucht React-Verstärkung im Team?

Wir bauen seit Jahren React- und Next.js-Anwendungen für Kunden in ganz Deutschland – als einzelner Experte, als Verstärkung fürs Bestandsteam oder als komplettes Scrum-Team.

Projekt besprechen →