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
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
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.
| Aspekt | useTransition | useDeferredValue |
|---|---|---|
| Eingang | Setter-Aufruf | Wert |
| Kontrolliere ich…? | …wann ich den State setze | …den abgeleiteten Wert |
| Pending-Flag | isPending | manuell: value !== deferredValue |
| Wann? | Wenn ich die Setter besitze | Wenn der Wert von außen kommt (Prop, Context) |
| Klassischer Use-Case | Lange Filterungen, Daten-Updates | Input-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.
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:
useTransitionist 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.