</>
{ }
React · Strict Mode · Double Invocation · useEffect · Debugging
React Strict Mode und
Double Invocation: warum Bugs dadurch sichtbar werden

„Meine App verhält sich im Development-Modus seltsam, im Production läuft sie einwandfrei" – dieser Satz ist ein Warnsignal, kein Entwarnung. React Strict Mode macht Bugs sichtbar, die in Production irgendwann auftreten werden. Das Doppelt-Ausführen ist kein Bug, sondern eine präzise Diagnosestrategie.

15 Min. Lesezeit StrictMode · Double Invocation · useEffect Cleanup · Idempotenz · Seiteneffekte React 18+ · TypeScript · Concurrent Mode

1. Was React Strict Mode wirklich macht

<React.StrictMode> ist ein Development-only-Werkzeug, das drei Dinge tut: Es führt Render-Funktionen und den Body von Komponenten zweimal aus (Double Invocation), es mountet und unmountet Effects direkt nach dem ersten Mount noch einmal, und es warnt bei der Verwendung veralteter APIs. Keines dieser Verhalten tritt im Production-Build auf. Das ist beabsichtigt: Strict Mode soll Fehler in der Entwicklungsphase aufdecken, die im Production-Modus erst unter bestimmten Bedingungen sichtbar werden – oder gar nicht, bis sie zu Datenverlust oder Inkonsistenz führen.

Ein weit verbreitetes Missverständnis: „Strict Mode ist nervig, ich deaktiviere ihn." Wer Strict Mode deaktiviert, deaktiviert einen Fehler-Detektor. Die Fehler, die Strict Mode sichtbar macht, existieren weiterhin im Code. In Production tauchen sie auf, wenn React intern Concurrent Features nutzt – Komponenten können mehrfach gerendert, unterbrochen und fortgesetzt werden. Strict Mode simuliert dieses Verhalten gezielt im Development-Modus, damit Probleme früh gefunden und behoben werden.

2. Warum Double Invocation kein Bug ist

Double Invocation – das doppelte Ausführen von Render-Funktionen – macht einen fundamentalen Anspruch sichtbar: Render-Funktionen müssen pure sein. Eine pure Funktion gibt bei denselben Inputs immer denselben Output zurück und hat keine Seiteneffekte. Wenn eine Render-Funktion einen Zähler inkrementiert, eine globale Variable schreibt oder einen API-Request abschickt, ist sie nicht pure. Dieses Problem existiert unabhängig vom Strict Mode, wird aber erst durch die doppelte Ausführung offensichtlich.

Die Idee hinter Double Invocation: Im Concurrent Mode von React 18 kann React die Ausführung einer Render-Funktion unterbrechen, verwerfen und neu starten. Das passiert zum Beispiel, wenn eine höher priorisierte Aktualisierung eingeht. Eine nicht-pure Render-Funktion – die Seiteneffekte produziert – verhält sich unter diesen Bedingungen falsch: Seiteneffekte werden doppelt ausgeführt, Zähler falsch inkrementiert, globale State korrumpiert. Strict Mode erzwingt das doppelte Ausführen im Development-Modus, damit solche Probleme früh erkannt werden, statt erst dann, wenn Concurrent Mode intern aktiv wird.


// Demonstration: impure render function exposed by Strict Mode
let renderCount = 0; // Global mutable state — anti-pattern

// WRONG: Side effect in render body
function BadCounter() {
  renderCount++; // Mutated in render — runs twice in Strict Mode
  console.log('Render count:', renderCount); // Logs 1, then 2, in one mount
  return <div>Count: {renderCount}</div>;
  // Shows 2 on first render — double invocation exposes the bug
}

// RIGHT: Pure render, effects in useEffect
function GoodCounter() {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    // Side effects belong here, not in render
    document.title = `Count: ${count}`;
  }, [count]);

  // Render is pure: same state → same output, no side effects
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

3. Der useEffect-Ablauf im Strict Mode

Der useEffect-Ablauf im Strict Mode ist das, was die meisten Entwickler am meisten verwirrt und überrascht. In der Produktion läuft ein Effect nach dem Mount einmal: Mount → Effect. Im Strict Mode im Development-Modus läuft es so: Mount → Effect → Cleanup → Effect. Das Muster ist also: Mount, Effect ausführen, Cleanup ausführen (als ob die Komponente unmountet), dann den Effect erneut ausführen. Das simuliert ein Unmount und Re-Mount der Komponente.

Warum dieses Muster? React 18 plant Features, bei denen Komponenten off-screen verschoben und wieder aktiviert werden können – ähnlich wie ein Tab, der nicht sichtbar ist, deaktiviert werden kann und beim Wiedersichtbarwerden seinen State wiederherstellt. Das bedeutet, Effects müssen ihren Zustand über Cleanup vollständig aufräumen können. Wenn der Effect nach einem Cleanup nicht denselben Zustand wiederherstellt wie nach dem ersten Mount, liegt ein Bug vor, der durch das Strict-Mode-Muster sofort sichtbar wird.

4. Welche Bugs-Kategorien sichtbar werden

Strict Mode macht vier Hauptkategorien von Bugs sichtbar. Erstens: Fehlende Cleanup-Logik in Effects. Ein Effect, der ein Intervall startet, ein Abonnement registriert oder eine Verbindung öffnet, ohne im Cleanup aufzuräumen, akkumuliert Ressourcen bei jedem Re-Mount. Im Strict Mode passiert das sofort: Zwei Intervalle laufen parallel, zwei WebSocket-Verbindungen sind offen, zwei Event-Listener registriert. In Production tritt das auf, wenn eine Komponente unmountet und wieder mountet – zum Beispiel beim Navigation in Single-Page-Apps.

Zweitens: Seiteneffekte im Render-Body. Alles, was in der Render-Funktion ausgeführt wird, muss frei von Seiteneffekten sein. Drittens: State-Inkonsistenz durch nicht-idempotente Effects. Wenn ein Effect State setzt, der nicht dieselbe Startbedingung wiederherstellt wie der Initialzustand, zeigt sich das im doppelten Effect-Lauf als falscher Ausgangszustand. Viertens: Race Conditions bei async Effects. Wenn ein Effect eine async Operation startet und die Komponente unmountet bevor die Operation fertig ist, kann der Callback State auf einer unmounteten Komponente setzen – der Cleanup muss solche laufenden Operationen abbrechen.


// Cleanup patterns for common Strict Mode bug categories

// 1. Timer: must clear on cleanup
function TimerComponent() {
  const [seconds, setSeconds] = React.useState(0);

  React.useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1); // Functional update — safe for double invocation
    }, 1000);

    // Cleanup: Strict Mode runs this between first and second Effect mount
    return () => clearInterval(interval);
    // Without cleanup: two intervals run after Strict Mode's double mount
  }, []);

  return <div>Seconds: {seconds}</div>;
}

// 2. Async: abort controller prevents state-on-unmounted
function DataFetcher({ userId }: { userId: string }) {
  const [data, setData] = React.useState<User | null>(null);

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

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

    // Cleanup: abort in-flight request on unmount or re-run
    return () => controller.abort();
    // Without cleanup: race condition on fast userId changes
  }, [userId]);

  return <div>{data?.name ?? 'Loading...'}</div>;
}

5. Korrekte Cleanup-Funktionen schreiben

Die Cleanup-Funktion eines useEffect ist das Gegenstück zum Setup: Sie soll den Zustand vollständig wiederherstellen, als wäre der Effect nie gelaufen. Dieser Symmetrie-Anspruch ist der Schlüssel zu korrekten Effects. Für jeden Ressourcentyp gibt es ein entsprechendes Cleanup-Pattern: Intervalle mit clearInterval, Timeouts mit clearTimeout, Event-Listener mit removeEventListener, Abonnements mit der vom Observable bereitgestellten Unsubscribe-Funktion, WebSocket-Verbindungen mit ws.close().

Ein häufiger Fehler: Die Cleanup-Funktion räumt zwar auf, aber mit der falschen Instanz. Das passiert, wenn die Ressource außerhalb des Effects in einem ref gespeichert wird und der Effect die Referenz überschreibt. Die korrekte Lösung: jede Ressource als lokale Variable im Effect deklarieren und genau diese Variable in der Cleanup-Funktion verwenden. Die Closure garantiert, dass die Cleanup-Funktion dieselbe Instanz kennt, die der Effect geöffnet hat. Diese Symmetrie macht Effects robust unter Concurrent Mode und nachvollziehbar für das Strict-Mode-Doppel-Muster.

6. Idempotenz: Effects, die zweimal laufen können

Idempotenz bedeutet, dass eine Operation mehrfach ausgeführt werden kann, ohne den Gesamtzustand zu verändern. Für React Effects: Ein Effect, der zweimal läuft, soll denselben Endzustand erzeugen wie ein Effect, der einmal läuft – vorausgesetzt, der Cleanup läuft dazwischen. Das ist die formale Anforderung, die Strict Mode überprüft. Wenn nach dem Zyklus „Effect → Cleanup → Effect" der Zustand sich von „einmaliger Effect" unterscheidet, liegt eine Idempotenz-Verletzung vor.

Ein klassisches nicht-idempotentes Muster: Ein Effect fügt ein DOM-Element ein. Beim ersten Lauf wird es eingefügt. Cleanup entfernt es. Beim zweiten Lauf wird es erneut eingefügt. Das ist korrekt. Ein nicht-korrektes Beispiel: Ein Effect fügt eine CSS-Klasse hinzu. Cleanup entfernt sie nicht. Beim zweiten Lauf ist die Klasse bereits vorhanden und wird nicht erneut hinzugefügt – aber der Zustand ist korrekt. Gefährlicher ist: Ein Effect registriert einen globalen Event-Listener. Cleanup entfernt ihn. Zweiter Lauf registriert ihn erneut. Korrekt. Aber ohne Cleanup: Zwei Listener, doppelte Events.

7. Drittbibliotheken und Strict Mode

Eine häufige Quelle von Strict-Mode-Problemen sind Drittbibliotheken, die nicht für doppelte Effect-Ausführung ausgelegt sind. Ältere Bibliotheken, die DOM-Elemente direkt manipulieren (Chart-Bibliotheken, Map-SDKs, Rich-Text-Editoren), initialisieren ihre Instanz typischerweise im componentDidMount oder einem useEffect und erwarten, dass diese Initialisierung genau einmal erfolgt. Im Strict Mode erfolgt sie zweimal – was zu Fehlern, doppelten Initialisierungen oder Abstürzen führt.

Die korrekte Lösung ist nicht, Strict Mode zu deaktivieren, sondern den Effect korrekt zu schreiben. Das Muster: Bibliothek in einem ref speichern, in der Cleanup-Funktion die Bibliotheks-Destroy-Methode aufrufen und das Ref auf null setzen. Wenn die Bibliothek keine Destroy-Methode hat, ist das selbst ein Bug der Bibliothek. In solchen Fällen hilft das Pattern mit einer boolean-Flag im Effect-Closure: let initialized = false; if (!initialized) { /* init */ initialized = true; } – das verhindert doppelte Initialisierung auch ohne Cleanup-Methode, ist aber ein Workaround, nicht die saubere Lösung.

8. Strict Mode und Concurrent Features in React 18

React 18 hat die Bedeutung von Strict Mode wesentlich erhöht. Mit Concurrent Features wie startTransition, useDeferredValue und dem Suspense-basierten Streaming kann React Render-Zyklen unterbrechen, neu priorisieren und wiederholen. Ein State-Update mit niedrigerer Priorität kann verworfen werden, wenn ein Update mit höherer Priorität eingeht – das bedeutet, die Render-Funktion wird mehrfach ausgeführt, und nicht alle davon führen zu einem Commit. Render-Funktionen müssen unter diesen Bedingungen pure sein, sonst entstehen nicht reproduzierbare Bugs.

Strict Mode in React 18 simuliert zusätzlich das Off-Screen-Verhalten für zukünftige React-Features: Komponenten werden deaktiviert und reaktiviert, ihre Effects werden dabei cleanup-ed und neu gemounted. Wer seinen Code Strict-Mode-kompatibel hält, bereitet sich auf zukünftige React-Features vor, ohne späteren Refactoring-Aufwand. Das ist der strategische Grund, warum React StrictMode auch nach Jahren noch alle neuen Checks bekommt: Es ist ein Kompatibilitäts-Prüfer für die Zukunft der React-Architektur.

9. Typische Bugs im Vergleich: falsch vs. korrekt

Die folgende Tabelle zeigt häufige Strict-Mode-Probleme und ihre korrekte Lösung. Jedes dieser Muster funktioniert im Production-Build zufällig oder zeitweise korrekt – und schlägt unter Concurrent Features oder beim Re-Mount fehl.

Kategorie Falsches Muster Korrektes Muster Symptom in Strict Mode
Intervall setInterval ohne clearInterval return () => clearInterval(id) Zwei Intervalle, doppelt schnelle Updates
Async Fetch Kein AbortController AbortController + return () => abort() Race Condition, State auf unmounteten Komponenten
Event Listener addEventListener ohne removeEventListener return () => removeEventListener() Doppelte Event-Ausführung
Render-Seiteneffekt API-Call im Render-Body API-Call nur in useEffect Doppelter API-Call beim Mount
Drittbibliothek Init ohne Destroy-Cleanup return () => instance.destroy() Doppelte Initialisierung, Fehler in Bibliothek

Das entscheidende Muster hinter allen korrekten Lösungen ist identisch: Symmetrie zwischen Setup und Cleanup. Was der Effect öffnet, muss der Cleanup schließen. Was der Effect registriert, muss der Cleanup deregistrieren. Was der Effect startet, muss der Cleanup stoppen. Diese Symmetrie macht Effects robust für alle Szenarien, die React intern durchläuft – Strict Mode, Concurrent Mode und zukünftige Off-Screen-Features.

Mironsoft

React Code Review · useEffect-Audit · Strict-Mode-Kompatibilität

React-App Strict-Mode-kompatibel machen?

Wir auditieren eure React-Codebase auf Strict-Mode-Inkompatibilitäten, fehlende Cleanup-Logik und nicht-pure Render-Funktionen – und beheben die Probleme bevor sie in Production auftreten.

Effect-Audit

Alle useEffect-Hooks auf fehlende Cleanup-Logik und Idempotenz prüfen

Render-Review

Seiteneffekte im Render-Body identifizieren und in korrekten Hook-Kontext verschieben

Bibliotheks-Integration

Drittbibliotheken Strict-Mode-kompatibel integrieren mit korrekten Destroy-Patterns

10. Zusammenfassung

React Strict Mode ist kein optionaler Komfort, sondern ein Diagnosewerkzeug, das Fehler aufdeckt, die in Production unter Concurrent Mode auftreten werden. Double Invocation erzwingt pure Render-Funktionen – Seiteneffekte im Render-Body werden durch doppelte Ausführung sofort sichtbar. Die doppelte Effect-Ausführung (Mount → Effect → Cleanup → Effect) prüft Cleanup-Korrektheit und Idempotenz: jeder Effect muss seinen Zustand vollständig über seine Cleanup-Funktion aufräumen können.

Die vier Hauptkategorien von Bugs, die Strict Mode aufdeckt: fehlende Cleanup-Logik, Seiteneffekte im Render, nicht-idempotente Effects und Race Conditions bei async Operations. Das korrekte Muster für alle Effects folgt demselben Prinzip: Symmetrie zwischen Setup und Cleanup. Was geöffnet wird, wird geschlossen. Was registriert wird, wird deregistriert. Was gestartet wird, wird gestoppt. Wer Effects so schreibt, schreibt Code, der auch in zukünftigen React-Versionen mit Off-Screen-Rendering und erweitertem Concurrent Mode korrekt funktioniert.

React Strict Mode — Das Wichtigste auf einen Blick

Double Invocation

Render-Funktionen zweimal ausführen – macht Seiteneffekte im Render sichtbar. Render muss pure sein: gleicher Input → gleicher Output, keine Nebeneffekte.

Effect-Zyklus

Mount → Effect → Cleanup → Effect. Cleanup muss Zustand vollständig aufräumen. Symmetrie: was Setup öffnet, schließt Cleanup.

Idempotenz

Effect zweimal mit Cleanup dazwischen = gleicher Endzustand wie einmaliger Effect. Intervalle, Listener, Verbindungen: immer Cleanup-Gegenstück.

Concurrent Mode

StrictMode simuliert React-18-Internals: unterbrechbares Rendering, Off-Screen-Features. Strict-Mode-kompatibel = zukunftssicher für künftige React-Features.

11. FAQ: React Strict Mode und Double Invocation

1Warum Komponenten zweimal ausführen?
Render muss pure sein. Concurrent Mode kann Renders unterbrechen und wiederholen. Double Invocation macht Seiteneffekte im Render sofort sichtbar.
2Ist das ein React-Bug?
Nein, absichtliche Diagnose. Nur im Development-Build aktiv. Macht Bugs sichtbar, die in Production unter Concurrent Mode auftreten.
3useEffect läuft zweimal – warum?
Mount → Effect → Cleanup (simuliertes Unmount) → Effect. Prüft ob Cleanup korrekt implementiert ist und Effect idempotent ist.
4Doppelten API-Call verhindern?
AbortController im Effect, in Cleanup abortieren. Erster Call wird abgebrochen, zweiter ist valide. Kein doppelter Call in Production.
5Strict Mode deaktivieren?
Nein. Deaktivieren verbirgt Bugs, löst sie nicht. Fehler treten in Production auf wenn Concurrent Features aktiv sind.
6Was ist die Cleanup-Funktion?
return () => {} in useEffect. Läuft bei Unmount und vor Effect-Neustart. Muss alle im Effect geöffneten Ressourcen schließen/aufräumen.
7Was ist Idempotenz bei Effects?
Zweimaliger Lauf mit Cleanup = gleicher Endzustand wie einmaliger Lauf. Setup und Cleanup müssen symmetrisch sein.
8Drittbibliotheken mit Strict Mode?
Instanz als lokale Variable im Effect. In Cleanup destroy() aufrufen. Ohne destroy(): boolean-Flag als Workaround oder neuere Bibliotheksversion suchen.
9Läuft Strict Mode in Production?
Nein. Ausschließlich im Development-Build. Production verhält sich normal ohne doppelte Ausführungen oder zusätzliche Checks.
10Strict Mode und Concurrent Features?
StrictMode simuliert Concurrent-Mode-Internals: unterbrechbares Rendering, Off-Screen-Aktivierung. Strict-Mode-kompatibel = zukunftssicher für React.