use ist eine API aus React 19, die beim Rendern einer Komponente einen Wert aus einem Promise oder einem Context liest. use(promise) pausiert die Komponente, bis das Promise aufgelöst ist, und zeigt in der Zwischenzeit den Fallback des nächsten <Suspense>; use(context) funktioniert wie useContext, darf aber in einem if oder einer Schleife stehen.
Die Funktion fetchUser unten steht für eine echte Anfrage.
Der erste Render zeigt eine Sekunde lang „Loading...“, dann das Profil. Klicke auf User 2: Der Fallback kommt zurück, solange das neue Promise aussteht. Profile hat keinen eigenen Ladezustand; es ist so geschrieben, als wären die Daten schon da.
Wie use(promise) funktioniert
Wenn Profile use(userPromise) aufruft:
- Wenn das Promise aufgelöst ist, gibt
useseinen Wert zurück, und das Rendern geht weiter. - Wenn es noch aussteht, hört React auf,
Profilezu rendern, und zeigt den Fallback des nächsten<Suspense>darüber. Wenn das Promise erledigt ist, rendert ReactProfileerneut, und diesmal gibtuseden Wert zurück. - Wenn es abgelehnt wurde, zeigt React stattdessen die nächste Error Boundary.
React merkt sich das Ergebnis am Promise-Objekt selbst. Deshalb muss das Promise beim nächsten Render dasselbe Objekt sein.
Um das alte Profil auf dem Bildschirm zu halten, während das nächste lädt, setze das Promise in einer Transition. React wartet dann, statt den Fallback erneut zu zeigen:
<button onClick={() => startTransition(() => setUserPromise(fetchUser(2)))}>User 2</button>
Wo das Promise entsteht
Das ist der Teil, an dem Leute stolpern. Den Fetch-Aufruf in die Komponente zu schreiben, die ihn liest, sieht natürlich aus und funktioniert nicht:
// Do not do this
function Profile({ id }) {
const user = use(fetchUser(id)); // a new promise on every render
return <p>{user.name}</p>;
}
Jeder Render ruft fetchUser auf und bekommt ein neues, ausstehendes Promise. Profile suspendiert. Wenn dieses Promise aufgelöst ist, rendert React Profile erneut, was ein weiteres ausstehendes Promise erzeugt, das wieder suspendiert. Die Komponente zeigt die Daten nie, und der Netzwerk-Tab füllt sich mit Anfragen.
Erzeuge das Promise an einer Stelle, die nicht bei jedem Render der lesenden Komponente erneut läuft:
- In einem Elternteil, im State gehalten (wie im ersten Beispiel) oder in einem Event-Handler erzeugt.
- Auf Modulebene, wenn die Daten nicht von Props abhängen:
const configPromise = fetchConfig();über der Komponente. - In einem Cache, nach den Argumenten geordnet, damit dieselbe ID dasselbe Promise zurückgibt.
- In einem Framework: Eine Server Component kann einer Client-Komponente ein Promise als Prop übergeben, und Route-Loader machen dasselbe. Für diesen Fall wurde
useentworfen.
Hier ist die Version mit Cache, damit eine Komponente Daten per ID anfragen und trotzdem ein stabiles Promise bekommen kann:
Klicke dich durch die drei Städte und dann zurück zu einer, die du schon geöffnet hast. Die Konsole loggt eine Anfrage pro Stadt, und eine Stadt, die du schon gesehen hast, erscheint ohne Fallback, weil ihr Promise schon aufgelöst ist. Lösch die Prüfung if (!cache.has(city)), und die Komponente suspendiert wieder endlos.
Ein echter Cache braucht außerdem einen Weg, Einträge ablaufen zu lassen. Datenbibliotheken und Frameworks erledigen das, deshalb bekommen die meisten Apps ihre Promises von dort.
use(context), sogar in einem if
use(SomeContext) gibt denselben Wert zurück wie useContext(SomeContext). Der Unterschied ist, wo du es aufrufen kannst. Jeder andere Hook muss auf oberster Ebene laufen, bei jedem Render in derselben Reihenfolge (die Regeln der Hooks). use darf nach einem frühen return, in einer Bedingung oder in einer Schleife aufgerufen werden.
Klicke auf den Button, um zwischen den beiden Zweigen zu wechseln. Tausche use(UserContext) gegen useContext(UserContext), und der Code bricht die Regeln der Hooks, weil der Hook bei manchen Renders liefe und bei anderen nicht.
use hat trotzdem Grenzen: Ruf es nur beim Rendern einer Komponente oder in einem Custom Hook auf. In einem Event-Handler oder einem Effekt liest du den Context über einen Wert, den du schon beim Rendern bekommen hast.
Fehler: Error Boundaries, nicht try/catch
Wenn das Promise abgelehnt wird, geht der Fehler an die nächste Error Boundary, genau wie ein Fehler beim Rendern.
Öffne Post 2, und die Boundary zeigt die Fehlermeldung. Öffne wieder Post 1, und es funktioniert: key={id} gibt der Boundary für jeden Post einen frischen State, damit sie nicht weiter den alten Fehler zeigt.
Ein ausstehendes oder abgelehntes use kannst du nicht mit try/catch abfangen. Solange das Promise aussteht, unterbricht use den Render, indem es einen besonderen Wert wirft, und ein catch darum würde ihn verschlucken. Der abgefangene Wert ist ein Fehler, dessen Meldung mit „Suspense Exception: This is not a real error!“ beginnt, und in der Entwicklung loggt React außerdem „use was called from inside a try/catch block“. Um statt eines Fehlerbildschirms einen Ersatzwert zu zeigen, behandle die Ablehnung am Promise, bevor du es weiterreichst:
const postPromise = fetchPost(id).catch(() => 'This post could not be loaded.');
use im Vergleich mit Laden per useEffect
Das klassische Muster auf dem Client lädt in einem Effekt und hält drei State-Werte:
function Profile({ id }) {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
let ignore = false;
fetchUser(id).then(
(u) => !ignore && setUser(u),
(e) => !ignore && setError(e),
);
return () => {
ignore = true;
};
}, [id]);
if (error) return <p>Error</p>;
if (!user) return <p>Loading...</p>;
return <p>{user.name}</p>;
}
useEffect | use | |
|---|---|---|
| Wann die Anfrage startet | nachdem der erste Render auf dem Bildschirm ist | wenn das Promise erzeugt wird, eventuell bevor die Komponente rendert |
| Ladezustand | dein eigenes if (!user) | das nächste <Suspense> |
| Fehler | dein eigener error-State | die nächste Error Boundary |
| Race Conditions | du schützt dich selbst (ignore) | die Komponente liest immer das Promise, das sie bekommen hat |
| Wer das Promise erzeugt | die Komponente | ein Elternteil, ein Cache oder ein Framework |
Die Version mit Effekt ist in sich geschlossen, deshalb ist sie in Apps ohne Framework weiterhin der häufigste Ansatz; die Seite zum Laden von Daten behandelt sie vollständig. use glänzt, wenn etwas über der Komponente die Anfrage früh startet und das Promise nach unten reicht.
Häufig gestellte Fragen
Was macht der use-Hook in React?
use(resource) gibt den Wert eines Promise oder eines Context zurück. Mit einem Promise suspendiert die Komponente, bis es aufgelöst ist, und das nächste <Suspense> zeigt seinen Fallback; wenn es abgelehnt wird, erscheint stattdessen die nächste Error Boundary.
Kann use bedingt aufgerufen werden?
Ja. use ist der einzige Hook, den du in if-Anweisungen, Schleifen und nach einem frühen return aufrufen kannst. Er muss trotzdem aus einer Komponente oder einem Hook aufgerufen werden, nie aus einem Event-Handler oder einem Effekt.
Warum läuft use(fetch()) in meiner Komponente endlos?
Jeder Render erzeugt ein neues Promise. Die Komponente suspendiert darauf, React rendert erneut, wenn es aufgelöst ist, und dieser Render erzeugt ein weiteres ausstehendes Promise. Erzeuge das Promise außerhalb des Renders: im State eines Elternteils, auf Modulebene, in einem Cache oder in einem Framework-Loader.
Kann ich use in try/catch packen?
Nein. Wenn ein Promise aussteht, unterbricht use den Render, indem es etwas wirft, und das abzufangen macht ihn kaputt. Behandle ein abgelehntes Promise mit einer Error Boundary oder hänge .catch() an das Promise, damit es zu einem Ersatzwert aufgelöst wird.
Sollte ich das Laden von Daten mit useEffect durch use ersetzen?
use braucht ein Promise, das einmal erzeugt und wiederverwendet wird, und genau das liefern Frameworks und Datenbibliotheken. In einer einfachen Client-App ist das Laden in einem Effekt (oder mit einer Bibliothek wie TanStack Query) weiterhin üblich und in Ordnung.