useDeferredValue (React 18+) ist das Werte-Pendant zu useTransition. Statt einen State-Setter als nicht-dringend zu markieren, übergibt man einen Wert und bekommt eine verzögerte Kopie zurück, die hinterher eilt. Während sich der Original-Wert hochfrequent ändert (z.B. ein Input-Wert), bleibt der verzögerte Wert beim letzten „stabilen" Wert stehen — bis React Zeit für ein nicht-dringendes Re-Render hat. Praktisch, wenn man eine teure abgeleitete UI (gefilterte Liste, gerendertes Diagramm) an einen schnell wechselnden Wert binden will, ohne die setState-Calls selbst zu kontrollieren (z.B. weil der Wert aus einer Prop oder einem Context kommt).

Signatur

TypeScript signatur.ts
const deferredValue = useDeferredValue(value);

useDeferredValue nimmt einen Wert und liefert eine Kopie, die bis zu einem Render hinterher läuft. Beim ersten Render sind beide identisch; danach kann der deferredValue zurückbleiben, bis React Zeit für ein nicht-dringendes Update hat.

Klassisches Beispiel — Suchfeld

TypeScript SuchListe.jsx
import { useState, useDeferredValue, useMemo } from 'react';

export default function SearchableList({ items }) {
    const [query, setQuery] = useState('');
    const deferredQuery = useDeferredValue(query);

    // Teure Berechnung — basiert auf dem deferredQuery
    const filtered = useMemo(() => {
        return items.filter(item =>
            item.name.toLowerCase().includes(deferredQuery.toLowerCase())
        );
    }, [items, deferredQuery]);

    const isStale = query !== deferredQuery;

    return (
        <>
            <input
                value={query}
                onChange={(e) => setQuery(e.target.value)}
            />
            <div style={{ opacity: isStale ? 0.5 : 1 }}>
                {filtered.map(item => <Item key={item.id} {...item} />)}
            </div>
        </>
    );
}

Das Input zeigt jeden Tastendruck sofort. Die gefilterte Liste hinkt nach — und das isStale-Flag macht das visuell sichtbar (z.B. mit reduzierter Opacity).

useDeferredValue vs. useTransition

Beide adressieren dasselbe Problem (UI-Responsivität bei teuren Re-Renders), aus unterschiedlichen Richtungen.

AspektuseTransitionuseDeferredValue
EingangSetter-AufrufWert
Kontrolliere ich…?…wann ich den State setze…den abgeleiteten Wert
Pending-FlagisPendingmanuell: value !== deferredValue
Wann?Wenn ich die Setter besitzeWenn der Wert von außen kommt (Prop, Context)
Klassischer Use-CaseLange Filterungen, Daten-UpdatesInput-gesteuerte teure UI

Wer den Setter kontrolliert: useTransition. Wer nur einen Wert empfängt: useDeferredValue.

Mit memo für maximale Wirkung

useDeferredValue bringt nichts, wenn die teure Komponente bei jedem Render trotzdem läuft. Damit React die teure Komponente überspringen kann, muss sie mit React.memo gewrappt sein.

TypeScript MitMemo.jsx
import { memo, useState, useDeferredValue } from 'react';

const ExpensiveList = memo(function ExpensiveList({ query }) {
    // Teure Berechnung
    const items = computeFilteredItems(query);
    return items.map(i => <Item key={i.id} {...i} />);
});

export default function App() {
    const [query, setQuery] = useState('');
    const deferredQuery = useDeferredValue(query);

    return (
        <>
            <input value={query} onChange={(e) => setQuery(e.target.value)} />
            <ExpensiveList query={deferredQuery} />
        </>
    );
}

Ohne memo rendert ExpensiveList bei jedem Eltern-Render — also bei jedem Tastendruck. Mit memo rendert sie nur, wenn sich der Prop-Wert ändert — und der ändert sich erst, wenn deferredQuery nachgezogen hat.

Wann NICHT useDeferredValue?

  • Wenn das Re-Render schnell genug ist — überflüssiger Overhead.
  • Wenn der Wert kritisch synchron sein muss (z.B. ein Cursor, der nicht hinterhereilen darf).
  • Wenn man die Setter selbst kontrolliert: useTransition ist klarer.

Besonderheiten

Erster Render: deferredValue === value.

Beim Mount sind beide identisch. Erst beim zweiten und folgenden Update kann der deferredValue zurückbleiben. Daher: kein „Skeleton-Loading" beim Mount.

React darf einen alten deferredValue nochmal liefern.

Wenn ein neuer Wert reinkommt, behält React kurz den alten als deferredValue. Erst nach erfolgreichem Hintergrund-Render schaltet er auf den neuen. Sichtbar als „Lag" zwischen value und deferredValue.

isStale-Indikator manuell — `value !== deferredValue`.

Anders als useTransition gibt's keinen automatischen isPending. Wer Loading anzeigen will, vergleicht selbst: const isStale = value !== deferredValue. Funktioniert bei Primitives via Reference-Identity.

Funktioniert nur in Kombination mit memo oder `useMemo`.

Ohne Memoization rendert die teure UI trotzdem bei jedem Eltern-Render. Der deferredValue ist nur „Empfehlung an React, was es als nicht-dringend behandeln darf" — die tatsächliche Skipping-Logik muss memo/useMemo liefern.

Concurrent-Feature — benötigt React 18+.

Vor React 18 gab's keine Concurrent-Rendering-API. useDeferredValue ist die idiomatische Form, ein Debounce-Pattern für teure UI zu bauen — ohne setTimeout-Hack.

useDeferredValue vs. Debounce — der Unterschied.

Debounce wartet eine feste Zeit (z.B. 300ms). useDeferredValue wartet, bis React „Zeit hat" — adaptiv an die Hardware/Browser-Last. Auf schnellen Geräten praktisch sofort, auf langsamen länger.

Object-Werte: Reference-Identity entscheidet.

useDeferredValue({a: 1}) mit einem neuen Object pro Render: jedes Mal als „neu" gewertet. Lösung: das Object stabilisieren (useMemo) oder Primitives verwenden.

React Doku: 'Verfügbarkeits-Hack' — nicht Performance-Allheilmittel.

Wenn die teure Komponente ITSELF langsam ist (CPU-bound), schiebt useDeferredValue das nur zeitlich. Echte Lösungen: Virtualisierung (react-window), Pagination, oder Algorithmus verbessern.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Hooks

Zur Übersicht