React Strict Mode: Warum Effekte zweimal ausgeführt werden und was das für Cleanup bedeutet
AI generated
{ }
React · Strict Mode · Effects
React Strict Mode und die doppelte Ausführung von Effects
Mount, Unmount, Mount: Warum das kein Bug ist, sondern eine bewusste Entwicklungshilfe

Wer zum ersten Mal einen console.log in einem useEffect sieht, der im Entwicklungs-Build plötzlich zweimal auftaucht, hält das fast immer für einen Fehler. Tatsächlich ist die doppelte Ausführung in React 18 und 19 unter Strict Mode bewusst eingebaut, um genau die Effects zu entlarven, die kein korrektes Cleanup besitzen und in der Produktion später zu schwer auffindbaren Bugs führen würden.

13 Min. Lesezeit Strict Mode useEffect Cleanup-Funktionen

1. Was Strict Mode überhaupt tut

Strict Mode ist keine zusätzliche Bibliothek, sondern eine in React eingebaute Entwicklungshilfe, die über die Komponente aktiviert wird und ausschließlich im Entwicklungs-Build wirkt. Sie verändert kein produktives Verhalten und erzeugt kein zusätzliches DOM, sondern schaltet innerhalb ihres Baums eine Reihe zusätzlicher Prüfungen scharf, die versteckte Probleme sichtbar machen sollen, bevor sie in Produktion zu echten Bugs werden.

Eine dieser Prüfungen betrifft Function-Komponenten und Hooks direkt: React ruft Render-Funktionen, bestimmte State-Updater und seit React 18 auch Effects im Strict-Mode-Baum absichtlich zweimal auf. Das mag beim ersten Kontakt widersprüchlich wirken, folgt aber einer klaren Logik: Statt auf zufällige Race Conditions oder seltene Nutzerinteraktionen zu warten, die ein Problem in Produktion erst nach Wochen sichtbar machen, provoziert Strict Mode dieselbe Klasse von Fehlern zuverlässig bei jedem einzelnen Render während der Entwicklung.

2. Warum ausgerechnet Effects zweimal laufen

Seit React 18 simuliert Strict Mode für jede Komponente, die gemountet wird, zusätzlich ein sofortiges Unmount und erneutes Mount. Konkret läuft dabei folgende Sequenz ab: Die Komponente mountet, ihre Effects laufen, React unmountet sie sofort wieder, ruft dabei alle Cleanup-Funktionen auf, und mountet sie dann erneut, wobei die Effects ein zweites Mal ausgeführt werden. Für den Nutzer im Browser ist davon nichts sichtbar, das gerenderte DOM bleibt stabil, nur die Effect-Logik im Hintergrund durchläuft diesen zusätzlichen Zyklus.

Der Grund dafür ist React 18s Concurrent-Rendering-Modell: Eine Komponente kann in einer laufenden Anwendung durchaus mehrfach gemountet werden, etwa wenn ein State im Speicher gehalten und eine Komponente später wieder eingeblendet wird, oder im zukünftigen Zusammenspiel mit Features wie Offscreen-Rendering. Effects, die beim erneuten Mount nicht sauber funktionieren, würden in solchen Szenarien in Produktion unbemerkt kaputtgehen. Die doppelte Ausführung im Entwicklungs-Build zwingt Entwickler, diesen Fall von Anfang an zu berücksichtigen, statt ihn erst durch einen Produktionsvorfall zu entdecken.

3. Wie fehlendes Cleanup dadurch sofort sichtbar wird

Ein Effect ohne Cleanup-Funktion, der etwa ein WebSocket öffnet, einen Intervall-Timer startet oder einen globalen Event-Listener registriert, hinterlässt beim simulierten Unmount eine Leiche: Die Ressource aus dem ersten Durchlauf bleibt bestehen, während der zweite Durchlauf eine weitere, identische Ressource anlegt. Statt einer einzelnen WebSocket-Verbindung bestehen plötzlich zwei, statt eines Intervalls laufen zwei parallel, und jeder eingehende Event wird doppelt verarbeitet.

Ohne Strict Mode bliebe dieses Muster meist unentdeckt, weil eine Komponente im typischen Entwicklungsalltag nur einmal mountet und der Effect entsprechend nur einmal läuft. Erst in Produktion, bei häufigem Navigieren zwischen Ansichten oder bei bestimmten React-18-Features, würden sich solche Ressourcenlecks über die Zeit ansammeln und zu doppelten Netzwerk-Requests, doppelt gefeuerten Analytics-Events oder spürbar steigendem Speicherverbrauch führen. Strict Mode macht genau dieses Leck schon beim ersten Testen sichtbar, weil sich die doppelte Ressource sofort im Netzwerk-Tab oder in der Konsole zeigt.


// Fehlerhaft: kein Cleanup, die Verbindung bleibt beim Unmount offen
function ChatRoom({ roomId }) {
  useEffect(() => {
    const socket = createConnection(roomId);
    socket.connect();
    // Kein return -> keine Cleanup-Funktion!
  }, [roomId]);

  return <p>Verbunden mit Raum {roomId}</p>;
}

// Im Strict Mode entstehen dadurch zwei offene Verbindungen
// nach jedem Mount, statt einer einzigen.

4. Effects mit korrektem Cleanup schreiben

Die Lösung ist in den meisten Fällen simpel: Jeder Effect, der eine Ressource anlegt, sollte über den return-Wert eine Cleanup-Funktion zurückgeben, die genau diese Ressource wieder abbaut. Für ein WebSocket bedeutet das einen expliziten disconnect-Aufruf, für einen Intervall-Timer ein clearInterval, für einen Event-Listener ein removeEventListener mit exakt derselben Funktionsreferenz, mit der er registriert wurde. React ruft diese Funktion sowohl beim echten Unmount als auch beim simulierten Unmount im Strict Mode auf.

Mit korrektem Cleanup läuft die Strict-Mode-Sequenz folgendermaßen ab: mount, Effect legt Verbindung an, simuliertes unmount, Cleanup schließt die Verbindung sauber, erneutes mount, Effect legt die Verbindung erneut an. Am Ende existiert exakt eine offene Verbindung, obwohl der Effect insgesamt zweimal gelaufen ist. Genau dieses Verhalten, am Ende ein sauberer, einfacher Zustand trotz doppelter Ausführung, ist das Kriterium dafür, dass ein Effect für React 18 und 19 korrekt geschrieben ist.


// Korrekt: Cleanup-Funktion baut die Ressource wieder ab
function ChatRoom({ roomId }) {
  useEffect(() => {
    const socket = createConnection(roomId);
    socket.connect();

    return () => {
      socket.disconnect();
    };
  }, [roomId]);

  return <p>Verbunden mit Raum {roomId}</p>;
}

5. Häufige Verwirrung beim ersten Kontakt mit Strict Mode

Der häufigste Reflex neu einsteigender Entwickler ist, die doppelte Ausführung als Bug in React selbst zu deuten und Strict Mode kurzerhand zu entfernen, um die Konsolen-Ausgabe wieder wie erwartet aussehen zu lassen. Das beseitigt zwar das Symptom, lässt aber das eigentliche Problem, den fehlenden oder fehlerhaften Cleanup, unangetastet im Code zurück, wo es in Produktion irgendwann doch auftritt, nur ohne die hilfreiche, frühe Warnung.

Ein zweites häufiges Missverständnis betrifft API-Aufrufe direkt im Effect ohne Abbruch-Logik: Ein doppelt ausgeführter fetch-Aufruf sieht im Netzwerk-Tab wie ein Bug aus, ist im Entwicklungsmodus aber erwartetes Verhalten, solange die zweite Antwort korrekt verarbeitet und nicht zu doppelten Server-Seiteneffekten führt. Für reine GET-Anfragen ohne Nebenwirkungen ist das meist unproblematisch, für POST-Anfragen mit Seiteneffekten wie dem Versenden einer E-Mail gehört ein Effect mit direktem API-Aufruf ohnehin eher in einen expliziten Event-Handler als in einen automatisch laufenden Effect.

6. Netzwerk-Requests mit AbortController sauber abbrechen

Für Datenabruf-Effects ist der AbortController das passende Cleanup-Werkzeug: Der Effect erstellt einen Controller, übergibt dessen signal an fetch, und die Cleanup-Funktion ruft controller.abort() auf. Im Strict Mode führt das dazu, dass der erste, durch das simulierte Unmount ausgelöste Request sauber abgebrochen wird, bevor seine Antwort überhaupt verarbeitet werden könnte, während der zweite Request regulär durchläuft und die Daten liefert.

Dieses Muster löst gleich zwei Probleme auf einmal: Es macht den Effect Strict-Mode-sicher, weil kein doppelter, unnötiger Request unbeobachtet im Hintergrund verbleibt, und es verhindert gleichzeitig die in einem früheren Artikel behandelte Race Condition bei schnell wechselnden Props, etwa einer sich schnell ändernden ID in der URL. Ein abgebrochener Request wirft in modernen Browsern einen AbortError, der im catch-Block gezielt ignoriert werden sollte, statt ihn wie einen echten Netzwerkfehler zu behandeln.


function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    fetch(`/api/users/${userId}`, { signal: controller.signal })
      .then((res) => res.json())
      .then((data) => setUser(data))
      .catch((err) => {
        if (err.name !== "AbortError") {
          console.error("Laden fehlgeschlagen", err);
        }
      });

    return () => controller.abort();
  }, [userId]);

  return user ? <p>{user.name}</p> : <p>Lädt...</p>;
}

7. Was Strict Mode ausdrücklich nicht verdoppelt

Es ist wichtig zu verstehen, dass Strict Mode nicht wahllos alles doppelt ausführt. Event-Handler wie onClick laufen weiterhin genau einmal pro Klick, ein regulärer fetch-Aufruf innerhalb eines Handlers wird nicht verdoppelt, und State-Updates aus Nutzerinteraktionen heraus verhalten sich unverändert. Betroffen sind gezielt die Render-Funktion selbst sowie die Mount-Phase von Effects, also genau jene Stellen, an denen React deterministisches, wiederholbares Verhalten voraussetzt.

Diese Eingrenzung ist bewusst: Würde React beliebigen imperativen Code in Event-Handlern verdoppeln, entstünde eine Flut falscher Alarme in Code, der ohnehin nur einmal pro Nutzeraktion laufen soll. Indem sich Strict Mode auf Render und Effect-Mount konzentriert, bleibt das Signal klar auf genau die Stellen fokussiert, an denen Nebenwirkungen ohne korrektes Cleanup tatsächlich zu echten Bugs in Produktion führen können.

8. Warum Strict Mode nicht einfach deaktiviert werden sollte

Der Impuls, StrictMode aus der Anwendung zu entfernen, sobald die doppelte Konsolen-Ausgabe stört, ist verständlich, aber kontraproduktiv: Strict Mode deckt Probleme in genau dem Moment auf, in dem sie am günstigsten zu beheben sind, nämlich während der aktiven Entwicklung an der betroffenen Komponente. Ein einmal entferntes Strict Mode kehrt in der Praxis selten zurück, und die eigentlich vorhandenen Cleanup-Lücken schlummern unentdeckt weiter im Code.

Der wirtschaftlich sinnvollere Weg ist, Strict Mode als festen Bestandteil der Entwicklungsumgebung zu behandeln und jede doppelte Log-Ausgabe oder doppelte Netzwerk-Anfrage als konkreten Hinweis auf fehlendes Cleanup zu lesen, statt sie zu ignorieren oder wegzukonfigurieren. Teams, die neu auf Strict Mode stoßen, profitieren davon, das Verhalten einmal bewusst im gesamten Projekt durchzugehen und systematisch jeden Effect auf eine passende Cleanup-Funktion zu prüfen, statt Fälle einzeln erst bei Beschwerden zu beheben.

9. Eine praktische Checkliste für Strict-Mode-sichere Effects

In der Praxis hat sich eine kurze, wiederholbare Prüfung bewährt: Legt der Effect eine Ressource an, sei es eine Verbindung, ein Timer, ein Listener oder ein Request? Falls ja, gibt es eine Cleanup-Funktion, die diese Ressource wieder exakt rückgängig macht? Und verhält sich der Effect beim zweiten Durchlauf identisch zum ersten, ohne sich auf einen bestimmten, nur einmal möglichen Ausgangszustand zu verlassen?

Diese drei Fragen lassen sich für jeden Effect im Code Review stellen und decken die überwiegende Mehrheit der Strict-Mode-Probleme ab. Die folgende Tabelle fasst typische Effect-Kategorien und das jeweils passende Cleanup-Muster noch einmal zusammen, als schnelle Referenz für den Alltag.

Effect-Typ Angelegte Ressource Passendes Cleanup Symptom ohne Cleanup
WebSocket-Verbindung socket.connect() socket.disconnect() im Return Doppelte offene Verbindungen, doppelte Events
Intervall oder Timeout setInterval / setTimeout clearInterval / clearTimeout im Return Zwei parallele Timer, doppelte Ausführung
Globaler Event-Listener addEventListener removeEventListener mit derselben Referenz Handler wird pro Event mehrfach aufgerufen
Datenabruf mit fetch fetch(url, { signal }) controller.abort() im Return Doppelter, unbeobachteter Request im Hintergrund

Mironsoft

React-Architektur, Performance und Magento-Frontend-Integration

React-Frontends, die schnell bleiben statt mit jedem Feature langsamer zu werden?

Wir prüfen bestehende React-Anwendungen auf unnötige Re-Renders, aufgeblähte Bundles und fragile State-Verwaltung und bauen daraus ein Frontend, das performant bleibt und sich sauber an Magento oder andere Backends anbindet.

Performance-Audit

Re-Renders, Bundle-Größe und Ladezeiten systematisch messen und beheben.

State-Architektur

Context, Zustand und Server State sauber trennen statt alles in einen Topf zu werfen.

Magento-Integration

GraphQL- oder REST-Anbindung an Magento robust und typsicher aufbauen.

10. Zusammenfassung

React Strict Mode Doppel-Effects: Das Wichtigste auf einen Blick

Was passiert

React 18 und 19 simulieren im Strict Mode pro Komponente: mount, unmount, erneutes mount, inklusive aller Effects.

Warum

Damit Effects ohne korrektes Cleanup zuverlässig bei jedem Testlauf auffallen, statt erst in Produktion Probleme zu verursachen.

Die Lösung

Jeder Effect, der eine Ressource anlegt, braucht eine Cleanup-Funktion, die diese Ressource exakt wieder abbaut.

Was betroffen ist

Render-Funktionen und die Mount-Phase von Effects, nicht aber Event-Handler oder reguläre Nutzerinteraktionen.

11. FAQ: React Strict Mode Doppel-Effects: Das Wichtigste auf einen Blick

1Ist die doppelte Ausführung von Effects im Strict Mode ein Bug?
Nein, sie ist eine bewusste, seit React 18 eingebaute Entwicklungshilfe. Sie simuliert mount, unmount und erneutes mount, um Effects ohne korrektes Cleanup schon während der Entwicklung sichtbar zu machen, statt erst in Produktion.
2Passiert die doppelte Ausführung auch in Produktion?
Nein, Strict Mode wirkt ausschließlich im Entwicklungs-Build. Im produktiven Build läuft jeder Effect genau einmal pro tatsächlichem Mount, die doppelte Simulation entfällt vollständig.
3Wie erkenne ich, dass ein Effect kein korrektes Cleanup hat?
Meist zeigt sich das an doppelten Netzwerk-Requests im DevTools-Netzwerk-Tab, doppelt gefeuerten Konsolen-Logs oder doppelt registrierten Event-Listenern nach dem ersten Mount einer Komponente unter Strict Mode.
4Sollte ich Strict Mode entfernen, wenn die doppelte Ausgabe stört?
Nein, das beseitigt nur das Symptom und lässt das eigentliche fehlende Cleanup im Code zurück, wo es in Produktion später unbemerkt zu Ressourcenlecks oder doppelten Seiteneffekten führen kann.
5Werden auch Event-Handler wie onClick im Strict Mode doppelt ausgeführt?
Nein, Strict Mode verdoppelt gezielt Render-Funktionen und die Mount-Phase von Effects, nicht aber Event-Handler. Ein Klick-Handler läuft weiterhin genau einmal pro tatsächlichem Klick.
6Wie schreibe ich einen Effect mit fetch, der Strict-Mode-sicher ist?
Über einen AbortController: Das signal wird an fetch übergeben, und die Cleanup-Funktion ruft controller.abort() auf. So wird der erste, durch das simulierte Unmount ausgelöste Request sauber abgebrochen, bevor er verarbeitet wird.
7Was passiert, wenn ein Effect ganz ohne return-Wert endet?
Dann registriert React keine Cleanup-Funktion für diesen Effect. Legt der Effect eine Ressource wie einen Timer oder eine Verbindung an, bleibt diese sowohl beim simulierten als auch beim echten Unmount ungenutzt bestehen, was als Ressourcenleck gilt.
8Gilt die doppelte Ausführung auch für useLayoutEffect?
Ja, useLayoutEffect unterliegt im Strict Mode derselben mount-unmount-mount-Simulation wie useEffect, weshalb auch dort jede angelegte Ressource eine passende Cleanup-Funktion benötigt.
9Verlangsamt Strict Mode die Entwicklungsumgebung spürbar?
Die doppelte Ausführung kostet etwas zusätzliche Rechenzeit im Entwicklungs-Build, das ist aber in der Praxis kaum spürbar. Da Strict Mode im produktiven Build vollständig entfällt, hat es keinerlei Einfluss auf die tatsächliche Anwendungsperformance.
10Muss ich Strict Mode manuell für jede Komponente aktivieren?
Nein, es reicht, den gewünschten Teilbaum, meist die gesamte Anwendung, einmal in einzuschließen. Alle darin gerenderten Komponenten und ihre Effects unterliegen dann automatisch den zusätzlichen Prüfungen.