Ein Button wird geklickt, Daten sollen geladen werden – und plötzlich feuert der Request zweimal. Oder eine Komponente wird verlassen, aber im Hintergrund läuft noch ein Timer. Viele dieser Probleme hängen an useEffect. Der Hook ist nicht „kompliziert“, aber er folgt festen Regeln. Wer diese Regeln kennt, kann Effects gezielt einsetzen – ohne Endlosschleifen, Memory-Leaks (liegengebliebene Ressourcen) und schwer reproduzierbare Fehler.
Warum useEffect existiert und was ein „Effect“ ist
React rendert UI (Benutzeroberfläche). Manche Dinge gehören aber nicht direkt ins Rendern, zum Beispiel:
- Daten vom Server holen
- Event-Listener (z. B. window resize) an- und abmelden
- Timer starten/stoppen
- Document-Title setzen
Solche Aufgaben heißen „Side Effects“ (Nebenwirkungen), weil sie etwas außerhalb des React-Renderings verändern. Genau dafür ist useEffect gedacht: Es führt Code nach einem Render aus.
Merksatz: Rendern beschreibt, Effect bewirkt
Der Render sollte idealerweise nur berechnen, wie die UI aussehen soll. Der Effect darf „die Außenwelt“ anfassen. Wenn beides vermischt wird, entstehen schnell Nebenwirkungen im falschen Moment (z. B. beim Re-Render).
Abhängigkeiten verstehen: Wann läuft ein Effect wirklich?
Die zweite Parameterposition von useEffect ist das Abhängigkeits-Array. Darin steht, wovon der Effect abhängt. Das ist der wichtigste Hebel, um Verhalten zu steuern.
Die drei Standard-Fälle
- Ohne Abhängigkeits-Array: Der Effect läuft nach jedem Render. Das ist selten sinnvoll und führt bei Fetching oft zu Endlosschleifen.
- Mit leerem Array
[]: Der Effect läuft nach dem ersten Render (Mount). In React Strict Mode (Entwicklungsmodus) kann es so wirken, als ob er doppelt läuft – dazu später mehr. - Mit Werten
[a, b]: Der Effect läuft nach dem ersten Render und danach immer, wenn sich a oder b ändert.
Warum „richtige“ Dependencies so entscheidend sind
Ein Effect „liest“ Werte aus seiner Umgebung (Props, State, Funktionen). Wenn ein gelesener Wert nicht im Array steht, arbeitet der Effect im schlimmsten Fall mit alten Daten (stale values). Andersherum gilt: Wenn das Array ständig „wechselnde“ Werte enthält (z. B. neu erzeugte Funktionen oder Objekte), läuft der Effect zu oft.
Die Regel ist simpel: Alles, was im Effect verwendet wird und von außen kommt, gehört grundsätzlich ins Array. Ausnahmen sind möglich, sollten aber bewusst und nachvollziehbar sein.
Daten laden ohne Chaos: Fetching-Muster für useEffect
Das häufigste Szenario ist ein API-Request. Hier entstehen typische Probleme: doppelte Requests, Rennen zwischen Antworten (Race Conditions) und Updates nach dem Unmount.
Fetching, sobald sich eine ID ändert
Beispiel: Eine Detailseite lädt Daten, sobald sich userId ändert. Wichtig sind: Loading-Status, Fehlerbehandlung und ein Abbruchmechanismus, falls die Komponente verschwindet.
Praktischer Ansatz: AbortController (Abbruch für fetch). Damit wird ein laufender Request abgebrochen, wenn der Effect neu startet oder die Komponente unmountet.
- Effect starten, Loading setzen
- fetch mit Signal starten
- Ergebnis nur verarbeiten, wenn nicht abgebrochen
- Cleanup: Controller aborten
So wird verhindert, dass eine späte Antwort noch State setzt, obwohl die UI schon weiter ist.
Warum Strict Mode „doppelte“ Requests zeigt
In React kann der Strict Mode in der Entwicklung bestimmte Abläufe absichtlich zweimal anstoßen. Das hilft, nicht robuste Effects zu erkennen (z. B. fehlendes Cleanup). In Produktion passiert das nicht in dieser Form. Trotzdem lohnt es sich, Effects so zu schreiben, dass zweimaliges Starten keine Schäden verursacht: idempotent (wiederholbar ohne Nebenwirkungen) und mit sauberem Cleanup.
Wann besser kein useEffect fürs Laden?
Nicht jedes Datenladen braucht einen Effect. Wenn eine Aktion eindeutig durch einen Benutzer-Click entsteht, ist ein Handler oft klarer: Beim Klick laden und State setzen. Effects sind besonders sinnvoll, wenn das Laden an Props/State gekoppelt ist (z. B. Filter, Pagination, Route-Parameter).
Cleanup richtig einsetzen: Listener, Timer und Subscriptions
Ein Effect darf eine Funktion zurückgeben. Diese Rückgabe ist das Cleanup. React ruft Cleanup auf, bevor der Effect erneut läuft und wenn die Komponente unmountet. Das ist der Ort, um aufzuräumen.
Event-Listener: immer abmelden
Ein häufiger Bug: Ein window-Listener wird bei jedem Re-Render erneut registriert, aber nie entfernt. Ergebnis: Der Handler läuft mehrfach und das UI wird träge.
Gutes Muster:
- Listener im Effect registrieren
- Cleanup: denselben Listener wieder entfernen
Wichtig: Der Funktions-Referenz muss stabil sein. Wenn der Handler bei jedem Render neu erzeugt wird, kann removeEventListener ins Leere laufen.
Timer: setInterval und setTimeout sauber stoppen
Timer sind klassische „vergessene“ Ressourcen. Im Cleanup gehört immer clearTimeout bzw. clearInterval. Wenn ein Timer von einem Wert abhängt (z. B. Intervall-Länge), sollte dieser Wert im Abhängigkeits-Array stehen. Dann startet React den Timer neu, wenn sich die Konfiguration ändert.
Typische Stolperfallen (und wie sie sich vermeiden lassen)
Endlosschleife durch State-Updates
Wenn ein Effect State setzt und dieser State im Abhängigkeits-Array steht, kann eine Schleife entstehen: Render → Effect → setState → Render → … Nicht jedes State-Update ist falsch, aber es muss begründet sein. Häufige Lösungen:
- Nur setzen, wenn sich der Wert wirklich geändert hat
- Abhängigkeiten reduzieren, indem Berechnungen in den Render verlegt werden
- Derived State vermeiden (State, der sich aus anderem State berechnen lässt)
„Stale“ Werte in Callbacks
Ein Effect registriert z. B. einen Listener, der auf aktuellen State zugreifen soll. Wenn der Listener aber mit alten Werten „eingefroren“ wurde, wirkt die App unlogisch. Typische Gegenmittel:
- Dependencies korrekt setzen, damit der Effect den Listener bei Änderungen neu registriert
- State über eine Ref spiegeln, wenn ein Listener bewusst stabil bleiben soll
Objekte und Funktionen als Dependencies
Objekte und Funktionen sind in JavaScript oft bei jedem Render „neu“, selbst wenn sie gleich aussehen. Dann läuft ein Effect ständig. Zwei pragmatische Strategien:
- Objekte/Funktionen außerhalb der Komponente definieren, wenn sie keine Props/State brauchen
- useMemo bzw. useCallback nutzen, um Referenzen zu stabilisieren (sparsam und gezielt einsetzen)
Kurze Praxis-Box für saubere Effects
- Effect nur für echte Nebenwirkungen verwenden (nicht für reine Berechnungen).
- Alle gelesenen Props/State/Funktionen in die Abhängigkeiten aufnehmen.
- Für Fetching: Abbruch oder „Ignore“-Mechanismus einbauen, damit kein State nach Unmount gesetzt wird.
- Für Listener/Timer: Cleanup immer implementieren und testen (Mount/Unmount).
- Wenn ein Effect „zu oft“ läuft: Ursachen prüfen (neue Objekt-/Funktionsreferenzen), dann erst memoizen.
Ein kleines Fallbeispiel: Filter-Suche mit Request-Abbruch
Angenommen, eine Suchliste lädt Ergebnisse, sobald sich ein Suchbegriff ändert. Ohne Schutz kann Folgendes passieren: Ein langsamer Request für „re“ kommt nach einem schnellen Request für „react“ zurück – und überschreibt die Ergebnisse mit veralteten Daten.
Ein robustes Muster:
- Suchbegriff ist Dependency des Effects
- Jeder neue Suchbegriff bricht den vorherigen Request ab
- Nur die letzte Antwort wird angezeigt
Das Ergebnis wirkt „stabil“: Die UI zeigt nicht plötzlich alte Treffer und lädt sauber nach.
Wann ein Effect zu viel ist: Alternativen für häufige Fälle
Werte ableiten statt in State speichern
Wenn sich ein Wert vollständig aus Props/State berechnen lässt, ist zusätzlicher State oft unnötig. Dann braucht es auch keinen Effect, der diesen State „synchron hält“. Beispiel: gefilterte Liste aus items und query. Besser direkt berechnen (oder bei Bedarf memoizen), statt im Effect zu setzen.
Event-getriebene Logik in Handlern lassen
Ein Submit-Button, der ein Formular sendet, braucht meist keinen Effect. Der Request gehört in den Submit-Handler. Das ist leichter zu verstehen, leichter zu testen und vermeidet Nebeneffekte beim Re-Render.
Verwandte Themen, die im Alltag oft zusammenhängen
useEffect taucht selten allein auf. Wer viele Nebenwirkungen hat, profitiert zusätzlich von sauberer Projektstruktur und klaren Abläufen:
- Promises in JavaScript verständlich einsetzen (wichtig für Fetching und Fehlerfälle)
- Event Loop verstehen (hilft bei Timing, Microtasks und „warum läuft das jetzt?“)
- Strukturierte Logs (wenn Effekte über API-Calls nachvollziehbar werden sollen)
Häufige Fragen aus der Praxis
- Muss wirklich jede verwendete Variable in die Abhängigkeiten? In der Regel ja. Wenn ein Wert sich ändern kann und im Effect genutzt wird, sollte er als Dependency rein, damit der Effect mit aktuellen Daten arbeitet.
- Warum läuft mein Effect „zu oft“, obwohl die Werte gleich sind? Häufig sind es neue Objekt- oder Funktionsreferenzen. Dann hilft es, die Referenz zu stabilisieren (z. B. mit useMemo/useCallback) oder die Struktur zu vereinfachen.
- Ist es schlimm, wenn ein Effect zweimal läuft? In der Entwicklung kann das durch Strict Mode passieren. Der Effect sollte so geschrieben sein, dass Wiederholungen keine unerwarteten Nebenwirkungen erzeugen und Cleanup korrekt ist.

