useEffect Hook
Verstehe den useEffect Hook für Seiteneffekte in React. API-Aufrufe, Timer, Event-Listener und Cleanup-Funktionen meistern.
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-Array | Wann laeuft der Effekt? |
|---|---|
| Keins | Bei 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
- Baue eine Uhr-Komponente, die jede Sekunde aktualisiert wird (mit Cleanup!)
- Erstelle eine User-Suche, die bei jeder Eingabe die API abfragt (mit Debounce)
- 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
useEffectist 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!