useEffect führt ein Stück Code aus, nachdem React deine Komponente gerendert und den Bildschirm aktualisiert hat. Du nutzt ihn, um eine Komponente mit etwas außerhalb von React synchron zu halten, etwa einem Timer, einer Netzwerkverbindung oder einem Event-Listener, und mit einem Abhängigkeitsarray steuerst du, wann er erneut läuft.
Öffne die Konsole unter der Vorschau: Der Effekt loggt einmal, wenn die Komponente erscheint, und dann nach jedem Klick erneut. Ändere [count] zu [] und klicke wieder: Nach dem ersten Log bleibt der Effekt still.
Die Syntax
useEffect(() => {
// setup: runs after the render is on the screen
return () => {
// cleanup: runs before the next setup, and on unmount
};
}, [dependencies]);
- Das erste Argument ist die Setup-Funktion. Sie kann eine Cleanup-Funktion zurückgeben oder nichts.
- Das zweite Argument ist das Abhängigkeitsarray: jeder Wert aus der Komponente, den das Setup liest (Props, State und alles, was daraus berechnet wird).
- Ruf
useEffectwie jeden Hook auf der obersten Ebene der Komponente auf.
Effekte laufen nach dem Rendern
Rendern heißt, dass React deine Komponentenfunktion aufruft, um herauszufinden, was der Bildschirm zeigen soll. Effekte warten, bis diese Arbeit erledigt und das DOM aktualisiert ist, also zeigt die Seite im Effekt schon die neuen Werte.
Jeder Klick loggt zuerst den Render und dann den Effekt, und der Effekt liest den Button-Text, den React gerade geschrieben hat. Deshalb gehören das Lesen des DOM, das Starten eines Timers oder das Verbinden mit einem Server in einen Effekt und nicht in den Komponentenkörper: Der Körper läuft beim Rendern, bevor irgendetwas auf dem Bildschirm ist, und React kann ihn öfter aufrufen, als du erwartest.
Effekte laufen meist, nachdem der Browser gezeichnet hat. Wenn ein Effekt das DOM vor dem Zeichnen messen muss, nutze stattdessen useLayoutEffect, der immer vor dem Zeichnen läuft.
Das Abhängigkeitsarray: drei Formen
Das Abhängigkeitsarray entscheidet, wann der Effekt erneut läuft. Es gibt drei Formen, und dieser Block nutzt alle, damit du den Unterschied beobachten kannst.
Beim Laden loggen alle drei Effekte. Klicke auf a + 1, und zwei davon laufen: der ohne Array und der, der von a abhängt. Klicke auf b + 1, und nur der ohne Array läuft, weil sich in [] und [a] nichts geändert hat.
| Abhängigkeitsarray | Wann der Effekt läuft |
|---|---|
| keins | Nach jedem Render |
[] | Einmal, nach dem ersten Render (beim Mounten) |
[a, b] | Nach dem ersten Render, dann immer wenn sich a oder b geändert hat |
React vergleicht jede Abhängigkeit mit ihrem vorherigen Wert per Object.is. Zahlen, Strings und Booleans werden nach Wert verglichen. Objekte, Arrays und Funktionen nach Identität, und das ist sehr wichtig (siehe unten).
Lass keinen Wert aus dem Array weg, damit ein Effekt seltener läuft. Der Effekt würde sonst weiter den Wert aus einem alten Render nutzen. Die Lint-Regel react-hooks/exhaustive-deps erkennt das; sie gehört zum ESLint-Setup, das das React-Template von Vite und die ESLint-Konfiguration von Next.js enthalten.
Cleanup
Viele Effekte starten etwas, das wieder gestoppt werden muss: eine Verbindung, ein Abonnement, einen Timer. Gib eine Funktion aus dem Effekt zurück, und React ruft sie zu zwei Zeitpunkten auf: direkt bevor der Effekt mit neuen Abhängigkeiten erneut läuft und wenn die Komponente den Bildschirm verlässt (Unmount).
Wähle einen anderen Raum: Die Konsole zeigt disconnect from general und dann connect to music. Die alte Verbindung schließt immer, bevor die neue öffnet. Klicke auf Hide, und das Cleanup läuft ein letztes Mal, weil ChatRoom unmountet wurde.
Die Funktion connect steht hier für einen echten Chat- oder WebSocket-Client. Die Form ist bei allen gleich: im Setup erzeugen, im Cleanup schließen.
Einen Timer löschen
Ein in einem Effekt gestarteter Timer läuft weiter, nachdem die Komponente weg ist, es sei denn, das Cleanup stoppt ihn.
Lösch die Zeile clearInterval(id); und versteck dann den Timer: tick kommt weiter in der Konsole an, von einer Komponente, die nicht mehr existiert. Jedes Show startet einen weiteren.
Achte auf setSeconds((s) => s + 1). Der Effekt läuft einmal, also würde ein einfaches setSeconds(seconds + 1) für immer seconds aus dem ersten Render lesen und bei 1 stehen bleiben. Die Updater-Funktion bekommt immer den neuesten Wert.
Einen Event-Listener entfernen
Event-Listener auf window oder document folgen demselben Muster. Übergib removeEventListener dieselbe Funktion, die du an addEventListener übergeben hast:
useEffect(() => {
function handleKeyDown(e) {
if (e.key === 'Escape') onClose();
}
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [onClose]);
Die Endlosschleife
Ein Effekt, der einen State-Wert setzt, von dem er auch abhängt, startet eine Schleife: Der State ändert sich, React rendert, die Abhängigkeit hat sich geändert, der Effekt läuft und setzt den State erneut.
// Never stops: every render runs the effect, every effect causes a render
useEffect(() => {
setCount(count + 1);
});
// Same loop: count changes, so the effect runs again
useEffect(() => {
setCount(count + 1);
}, [count]);
Die Komponente rendert ohne Ende, und der Tab bleibt beschäftigt. React stoppt diese Schleife nicht: In der Entwicklung loggt es einen Fehler „Maximum update depth exceeded“ in die Konsole, in der Produktion loggt es nichts. Dieser Block führt die zweite Version mit einer Abbruchbedingung aus, damit du die Schleife beobachten kannst, ohne die Seite einzufrieren:
Die Konsole loggt render 0 bis render 5: sechs Renders für ein Mounten, jeder vom Effekt ausgelöst. Die Lösung hängt davon ab, was du gemeint hast:
- Du wolltest es einmal. Nutze
[]und die Updater-Form,setCount((c) => c + 1), damitcountkeine Abhängigkeit ist. - Du berechnest einen Wert aus anderem State. Speichere ihn gar nicht; berechne ihn beim Rendern, wie im letzten Abschnitt dieser Seite gezeigt.
- Du reagierst auf eine Aktion des Nutzers. Setze den Code in den Event-Handler.
Objekte und Funktionen als Abhängigkeiten
Ein Objekt, Array oder eine Funktion, die im Komponentenkörper erzeugt wird, ist bei jedem Render ein neuer Wert, also läuft ein Effekt, der davon abhängt, nach jedem Render, auch wenn sich darin nichts geändert hat.
Das Umschalten der Farben hat nichts mit der Suche zu tun, und doch loggt jedes Umschalten erneut search for react. Verschiebe die Zeile options in den Effekt und ändere das Array zu [query]: Das Umschalten bleibt still, und nur das Tippen ins Input führt die Suche aus.
Das ist die allgemeine Lösung. Erzeuge Objekte und Funktionen in dem Effekt, der sie nutzt, und hänge von den primitiven Werten ab, aus denen sie gebaut werden. Wenn eine Funktion außerhalb des Effekts leben muss, hält useCallback ihre Identität zwischen Renders stabil.
Warum Effekte in der Entwicklung zweimal laufen
Mit eingeschaltetem <StrictMode> (eine neue Vite-App umschließt den Root damit, und der App Router von Next.js schaltet ihn standardmäßig ein) führt React in der Entwicklung einen Test durch: Es mountet jede Komponente, führt ihre Effekte aus, führt deren Cleanups aus und führt die Effekte erneut aus. Für den Chatraum oben zeigt die Konsole in der Entwicklung:
connect to general
disconnect from general
connect to general
Das passiert einmal, beim Mounten, und nur in der Entwicklung. Es existiert, um Effekte aufzudecken, denen ein Cleanup fehlt: Wenn zwei Durchläufe hintereinander etwas kaputt machen, würde auch der Nutzer es kaputt machen, indem er weggeht und zurückkommt. Die Lösung ist nie, den zweiten Durchlauf mit einer Ref zu blockieren, sondern das Cleanup zu schreiben. Die Vorschau auf dieser Seite läuft wie ein Produktions-Build, also siehst du den doppelten Durchlauf hier nicht.
Vielleicht brauchst du keinen Effekt
Effekte sind dafür da, sich mit Systemen außerhalb von React zu synchronisieren. Zwei häufige Verwendungen sind ohne Effekt besser.
Werte, die aus Props oder State berechnet werden. Berechne sie beim Rendern. Sie im State zu speichern und mit einem Effekt zu synchronisieren kostet einen zusätzlichen Render, und der erste dieser Renders setzt einen veralteten Wert ins DOM:
Jeder Klick rendert WithEffect zweimal, und sein erster Render zeigt noch den alten Namen. Computed rendert einmal mit dem richtigen Wert.
Code, der auf eine Aktion des Nutzers reagiert. Ein Formular senden, nach einem Kauf einen Toast zeigen oder für einen Klick ein Analytics-Event senden gehört in den Event-Handler. Dort weißt du genau, was passiert ist, während ein Effekt nur weiß, dass sich irgendein Wert geändert hat.
useEffectEvent
Manchmal braucht ein Effekt den neuesten Wert einer Prop, ohne erneut zu laufen, wenn sich diese Prop ändert. Im Chatraum soll eine Benachrichtigung das aktuelle theme nutzen, aber ein Wechsel des Themes soll nicht neu verbinden. React 19.2 bringt dafür useEffectEvent:
import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme); // always the latest theme
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', () => onConnected());
connection.connect();
return () => connection.disconnect();
}, [roomId]); // theme is not a dependency
}
Eine Funktion aus useEffectEvent ist nur zum Aufrufen innerhalb von Effekten gedacht. Übergib sie nicht an andere Komponenten und führe sie nicht in einem Abhängigkeitsarray auf.
Häufig gestellte Fragen
Wann läuft useEffect?
Nachdem React die Komponente gerendert und das DOM aktualisiert hat, meist nachdem der Browser gezeichnet hat. Ohne Abhängigkeitsarray läuft er nach jedem Render, mit [] nur nach dem ersten und mit [a, b] nach dem ersten Render und nach jedem Render, in dem sich a oder b geändert hat.
Was ist die Cleanup-Funktion von useEffect?
Die Funktion, die du aus dem Effekt zurückgibst. React ruft sie auf, bevor der Effekt mit neuen Abhängigkeiten erneut läuft, und noch einmal, wenn die Komponente vom Bildschirm entfernt wird. Nutze sie, um Timer zu löschen, Event-Listener zu entfernen und Abonnements zu schließen.
Warum läuft mein useEffect zweimal?
In der Entwicklung mountet <StrictMode> jede Komponente, führt ihre Effekte aus, räumt sie auf und führt sie erneut aus, um dir Effekte zu zeigen, denen ein Cleanup fehlt. Das passiert einmal beim Mounten und nur in der Entwicklung. Produktions-Builds führen den Effekt einmal aus.
Warum verursacht mein useEffect eine Endlosschleife?
Der Effekt setzt einen State-Wert, der auch in seinem Abhängigkeitsarray steht (oder er hat gar kein Array). Das Setzen des States rendert erneut, die Abhängigkeit hat sich geändert, also läuft der Effekt erneut. Nutze eine Updater-Funktion und entferne die Abhängigkeit, füge eine Bedingung hinzu oder berechne den Wert stattdessen beim Rendern.
Kann der useEffect-Callback async sein?
Nein. Der Callback muss nichts oder eine Cleanup-Funktion zurückgeben, und eine async-Funktion gibt ein Promise zurück. Definiere im Effekt eine async-Funktion und ruf sie auf, wie auf der Seite zum Laden von Daten gezeigt.