Zum Inhalt springen
React Anfänger 35 min

useEffect Hook

Verstehe den useEffect Hook für Seiteneffekte in React. API-Aufrufe, Timer, Event-Listener und Cleanup-Funktionen meistern.

Aktualisiert:

useEffect Hook

Der useEffect-Hook ist dein Werkzeug fuer Seiteneffekte in React. Alles, was ausserhalb des normalen Renderns passiert – API-Aufrufe, Timer, DOM-Manipulationen oder Event-Listener – gehoert in einen useEffect.

Was sind Seiteneffekte?

Seiteneffekte sind Aktionen, die ueber das reine Berechnen von JSX hinausgehen:

// Kein Seiteneffekt - reines Rendern
function Greeting({ name }) {
  return <h1>Hallo, {name}!</h1>;
}

// Seiteneffekte - brauchen useEffect
// - Daten von einer API laden
// - Den Dokument-Titel aendern
// - Einen Timer starten
// - Event-Listener hinzufuegen

Grundlegende Syntax

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    // Dieser Code laeuft NACH dem Rendern
    document.title = `${count} Klicks`;
  });

  return (
    <div>
      <p>Du hast {count} mal geklickt.</p>
      <button onClick={() => setCount(count + 1)}>Klick mich</button>
    </div>
  );
}

Das Dependency-Array

Das zweite Argument von useEffect kontrolliert, wann der Effekt laeuft:

Kein Dependency-Array – laeuft bei jedem Render

useEffect(() => {
  console.log('Laeuft bei JEDEM Render');
});

Leeres Array – laeuft nur einmal (beim Mounten)

useEffect(() => {
  console.log('Laeuft nur EINMAL beim Start');
}, []);

Mit Dependencies – laeuft wenn sich Werte aendern

useEffect(() => {
  console.log('Laeuft wenn sich count aendert');
  document.title = `Zaehler: ${count}`;
}, [count]);

Vergleichstabelle

Dependency-ArrayWann laeuft der Effekt?
KeinsBei jedem Render
[]Nur beim ersten Render (Mounten)
[a, b]Wenn sich a oder b aendert

API-Daten laden

Der haeufigste Anwendungsfall fuer useEffect:

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchUsers() {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/users');
        if (!response.ok) throw new Error('Fehler beim Laden');
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchUsers();
  }, []); // Leeres Array = nur einmal laden

  if (loading) return <p>Laden...</p>;
  if (error) return <p>Fehler: {error}</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name} ({user.email})</li>
      ))}
    </ul>
  );
}

Daten abhaengig von Props laden

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

  useEffect(() => {
    setLoading(true);

    fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]); // Laeuft erneut wenn sich userId aendert

  if (loading) return <p>Laden...</p>;
  if (!user) return <p>User nicht gefunden.</p>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

Cleanup-Funktionen

Manche Effekte muessen aufraeumen, wenn die Komponente entfernt wird oder bevor der Effekt erneut laeuft:

useEffect(() => {
  // Effekt starten

  return () => {
    // Cleanup - wird ausgefuehrt wenn:
    // 1. Die Komponente entfernt wird (Unmount)
    // 2. Bevor der Effekt erneut laeuft
  };
}, [dependencies]);

Timer aufraeumen

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

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

  return <p>Zeit: {seconds} Sekunden</p>;
}

Event-Listener aufraeumen

function WindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    function handleResize() {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }

    window.addEventListener('resize', handleResize);

    // Cleanup: Event-Listener entfernen
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return (
    <p>Fenstergroesse: {size.width} x {size.height}</p>
  );
}

Fetch abbrechen

function SearchResults({ query }) {
  const [results, setResults] = useState([]);

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

    async function search() {
      try {
        const response = await fetch(
          `/api/search?q=${query}`,
          { signal: controller.signal }
        );
        const data = await response.json();
        setResults(data);
      } catch (err) {
        if (err.name !== 'AbortError') {
          console.error('Suchfehler:', err);
        }
      }
    }

    if (query) search();

    // Cleanup: Vorherigen Request abbrechen
    return () => controller.abort();
  }, [query]);

  return (
    <ul>
      {results.map(r => <li key={r.id}>{r.title}</li>)}
    </ul>
  );
}

Mehrere useEffects

Trenne verschiedene Seiteneffekte in eigene useEffects:

function UserDashboard({ userId }) {
  const [user, setUser] = useState(null);
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  // Effekt 1: User laden
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]);

  // Effekt 2: Online-Status ueberwachen
  useEffect(() => {
    function handleOnline() { setIsOnline(true); }
    function handleOffline() { setIsOnline(false); }

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  // Effekt 3: Dokument-Titel
  useEffect(() => {
    if (user) {
      document.title = `${user.name} - Dashboard`;
    }
    return () => { document.title = 'Meine App'; };
  }, [user]);

  return (
    <div>
      {!isOnline && <p style={{ color: 'red' }}>Du bist offline!</p>}
      {user && <h1>Willkommen, {user.name}!</h1>}
    </div>
  );
}

Haeufige Fehler

Fehler 1: Endlosschleife

// FALSCH - Erstellt bei jedem Render ein neues Objekt
useEffect(() => {
  fetchData();
}, [{ page: 1 }]); // Objekt-Referenz aendert sich jedes Mal!

// RICHTIG - Primitive Werte oder Variablen verwenden
const page = 1;
useEffect(() => {
  fetchData();
}, [page]);

Fehler 2: Fehlende Dependencies

// FALSCH - count fehlt in den Dependencies
useEffect(() => {
  const interval = setInterval(() => {
    setCount(count + 1); // count ist immer der Anfangswert!
  }, 1000);
  return () => clearInterval(interval);
}, []);

// RICHTIG - Updater-Funktion verwenden
useEffect(() => {
  const interval = setInterval(() => {
    setCount(prev => prev + 1); // Funktioniert korrekt
  }, 1000);
  return () => clearInterval(interval);
}, []);

Fehler 3: async in useEffect

// FALSCH - useEffect darf keine async-Funktion sein
useEffect(async () => {
  const data = await fetchData();
}, []);

// RICHTIG - async-Funktion innerhalb definieren
useEffect(() => {
  async function loadData() {
    const data = await fetchData();
  }
  loadData();
}, []);

Uebungen

  1. Baue eine Uhr-Komponente, die jede Sekunde aktualisiert wird (mit Cleanup!)
  2. Erstelle eine User-Suche, die bei jeder Eingabe die API abfragt (mit Debounce)
  3. Baue einen Dark-Mode-Toggle, der die Einstellung im localStorage speichert und beim Laden wiederherstellt

Was kommt als Naechstes?

Du verstehst jetzt useEffect fuer Seiteneffekte. Im naechsten Kapitel lernst du den useRef Hook kennen – damit kannst du auf DOM-Elemente zugreifen und Werte speichern, die keinen Re-Render ausloesen.

Zusammenfassung

  • useEffect ist fuer Seiteneffekte: API-Aufrufe, Timer, Event-Listener
  • Das Dependency-Array kontrolliert, wann der Effekt laeuft
  • Cleanup-Funktionen raeumen Ressourcen auf (Timer, Listener, Requests)
  • Trenne verschiedene Effekte in separate useEffects
  • Definiere async-Funktionen innerhalb des useEffect, nicht als Callback
  • Nutze Updater-Funktionen in Intervallen statt direktem State-Zugriff

Pro-Tipp: Vergiss nie die Cleanup-Funktion! Fehlende Cleanups sind eine der haeufigsten Ursachen fuer Memory Leaks und schwer zu findende Bugs in React-Apps. Wenn du einen Listener hinzufuegst, raeume ihn auf. Wenn du einen Timer startest, stoppe ihn!

Zurück zum React Kurs