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.
Inhaltsverzeichnis
- 1. Was Strict Mode überhaupt tut
- 2. Warum ausgerechnet Effects zweimal laufen
- 3. Wie fehlendes Cleanup dadurch sofort sichtbar wird
- 4. Effects mit korrektem Cleanup schreiben
- 5. Häufige Verwirrung beim ersten Kontakt mit Strict Mode
- 6. Netzwerk-Requests mit AbortController sauber abbrechen
- 7. Was Strict Mode ausdrücklich nicht verdoppelt
- 8. Warum Strict Mode nicht einfach deaktiviert werden sollte
- 9. Eine praktische Checkliste für Strict-Mode-sichere Effects
- 10. Zusammenfassung
- 11. FAQ
1. Was Strict Mode überhaupt tut
Strict Mode ist keine zusätzliche Bibliothek, sondern eine in React eingebaute Entwicklungshilfe, die über die Komponente
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.