Der useEffect-Hook in React: Seiteneffekte richtig steuern

Datenabruf, Timer, Event-Listener: useEffect ist dein Werkzeug für Seiteneffekte in React. Du lernst das Dependency-Array, das Cleanup und die häufigsten Stolperfallen kennen.

Teilen

In React beschreibst du mit deinen Komponenten normalerweise nur, wie die Oberfläche aussehen soll. Doch früher oder später musst du etwas tun, das über das reine Rendern hinausgeht: Daten von einer API laden, einen Timer starten, einen Event-Listener registrieren oder den Dokumententitel ändern. Solche Aktionen nennt man Seiteneffekte (englisch side effects), und genau dafür gibt es den useEffect-Hook. In diesem Beitrag lernst du, wie er funktioniert, was das Dependency-Array bedeutet und wie du typische Fehler vermeidest.

Was ist ein Seiteneffekt?

Eine React-Komponente sollte im Idealfall eine reine Funktion sein: Aus den gleichen Props und dem gleichen State entsteht immer die gleiche Ausgabe. Alles, was außerhalb dieses Ablaufs mit der Welt interagiert, ist ein Seiteneffekt – zum Beispiel ein Netzwerk-Request oder der Zugriff auf document. Diese Dinge dürfen nicht direkt im Rendering passieren, weil React eine Komponente jederzeit und mehrfach neu ausführen kann.

useEffect gibt dir einen kontrollierten Ort für solchen Code: Die Funktion, die du übergibst, läuft nach dem Rendern, wenn das DOM bereits aktualisiert ist. Ein erstes Beispiel:

import { useState, useEffect } from "react";

function Titel() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `Du hast ${count} Mal geklickt`;
  });

  return <button onClick={() => setCount(count + 1)}>Klick mich</button>;
}

Nach jedem Rendern setzt der Effekt den Dokumententitel neu. So weit, so einfach – aber genau hier lauert schon die erste wichtige Frage: Wie oft soll der Effekt eigentlich laufen?

Das Dependency-Array steuert alles

Der zweite Parameter von useEffect ist das Dependency-Array. Er entscheidet, wann der Effekt erneut ausgeführt wird. Es gibt drei Varianten:

  • Kein Array: Der Effekt läuft nach jedem Render.
  • Leeres Array []: Der Effekt läuft nur einmal, nämlich nach dem ersten Rendern (dem Mount).
  • Array mit Werten [a, b]: Der Effekt läuft nur dann erneut, wenn sich einer dieser Werte seit dem letzten Render geändert hat.
// Nur einmal beim Mounten
useEffect(() => {
  console.log("Komponente wurde eingehängt");
}, []);

// Nur wenn sich userId ändert
useEffect(() => {
  console.log("Lade Daten für", userId);
}, [userId]);

Die wichtigste Regel lautet: Alles, was du im Effekt aus dem Komponenten-Scope verwendest – Props, State, Funktionen – gehört ins Dependency-Array. Lässt du etwas weg, arbeitet dein Effekt womöglich mit veralteten Werten. Das offizielle ESLint-Plugin eslint-plugin-react-hooks warnt dich zuverlässig vor solchen fehlenden Abhängigkeiten.

Cleanup: Aufräumen nicht vergessen

Manche Effekte hinterlassen Spuren, die du wieder beseitigen musst: ein Timer läuft weiter, ein Event-Listener bleibt hängen, eine WebSocket-Verbindung bleibt offen. Dafür kannst du aus dem Effekt eine Cleanup-Funktion zurückgeben. React ruft sie auf, bevor der Effekt erneut läuft und wenn die Komponente entfernt wird (Unmount).

function Uhr() {
  const [zeit, setZeit] = useState(new Date());

  useEffect(() => {
    const id = setInterval(() => {
      setZeit(new Date());
    }, 1000);

    // Cleanup: Timer beim Unmount stoppen
    return () => clearInterval(id);
  }, []);

  return <p>{zeit.toLocaleTimeString()}</p>;
}

Ohne das clearInterval würde bei jedem erneuten Effekt-Lauf ein weiterer Timer starten – ein klassisches Speicherleck. Merke dir: Wenn ein Effekt etwas abonniert oder startet, sollte er es im Cleanup auch wieder beenden.

Daten laden mit useEffect

Der wohl häufigste Anwendungsfall ist das Laden von Daten. Weil die Effekt-Funktion selbst nicht async sein darf (sie müsste sonst ein Promise zurückgeben, was React als Cleanup missverstehen würde), definierst du eine innere async-Funktion:

function Benutzerprofil({ userId }) {
  const [user, setUser] = useState(null);
  const [laedt, setLaedt] = useState(true);

  useEffect(() => {
    let aktiv = true;
    setLaedt(true);

    async function ladeUser() {
      const res = await fetch(`/api/users/${userId}`);
      const daten = await res.json();
      if (aktiv) {
        setUser(daten);
        setLaedt(false);
      }
    }

    ladeUser();
    return () => { aktiv = false; };
  }, [userId]);

  if (laedt) return <p>Lädt ...</p>;
  return <h2>{user.name}</h2>;
}

Die Variable aktiv ist ein wichtiges Detail: Wechselt der Nutzer schnell die userId, laufen mehrere Requests gleichzeitig. Über das Flag ignorierst du die Antwort eines veralteten Requests und vermeidest, dass ein zu spät eintreffendes Ergebnis das neuere überschreibt.

Häufige Stolperfallen

Ein paar Fehler tauchen bei Einsteigern immer wieder auf. Wenn du sie kennst, sparst du dir viel Frust:

  • Endlosschleifen: Wenn du im Effekt einen State setzt, von dem der Effekt selbst abhängt, rendert die Komponente endlos neu. Prüfe dein Dependency-Array genau.
  • Objekte und Arrays als Abhängigkeit: Sie werden bei jedem Render neu erzeugt und gelten daher als „geändert“. Nutze primitive Werte oder stabilisiere sie mit useMemo.
  • useEffect für abgeleitete Werte: Wenn du nur aus vorhandenem State einen neuen Wert berechnest, brauchst du keinen Effekt – berechne ihn einfach direkt beim Rendern.

Der letzte Punkt ist besonders wichtig. Nicht jede Aufgabe braucht useEffect. Ein Effekt ist für die Synchronisation mit externen Systemen gedacht, nicht als Allzweckwerkzeug für jede Berechnung.

Fazit

Mit useEffect bringst du Seiteneffekte kontrolliert in deine Funktionskomponenten. Der Schlüssel zum Verständnis ist das Dependency-Array: Es bestimmt, wann dein Effekt läuft, und muss alle verwendeten Werte enthalten. Vergiss die Cleanup-Funktion nicht, wenn dein Effekt Timer, Listener oder Verbindungen anlegt, und setze useEffect gezielt für die Synchronisation mit der Außenwelt ein – nicht für Dinge, die du auch direkt beim Rendern erledigen kannst. Wenn du diese Prinzipien verinnerlichst, wird aus einem der anfangs verwirrendsten Hooks ein verlässliches Werkzeug in deinem React-Alltag.