React Hooks haben die Art, React-Komponenten zu schreiben, revolutioniert. Seit React 16.8 ermoglichen Hooks State, Lifecycle und mehr in funktionalen Komponenten. Dieser vollstandige React Hooks Guide behandelt jeden Hook mit praktischen Beispielen.
useState: Komponentenstate verwalten
useState ist der grundlegendste Hook.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(prev => prev - 1)}>Decrement</button>
</div>
);
}
// Lazy initialization â runs only on first render
const [data, setData] = useState(() => {
return JSON.parse(localStorage.getItem('data') || '{}');
});
// Updating objects â always create a new object
const [user, setUser] = useState({ name: '', age: 0 });
setUser(prev => ({ ...prev, name: 'Alice' }));
// Updating arrays â use spread or filter/map
const [items, setItems] = useState<string[]>([]);
setItems(prev => [...prev, 'new item']);
setItems(prev => prev.filter(item => item !== 'remove me'));useEffect: Seiteneffekte und Lifecycle
useEffect ermoglicht Seiteneffekte in Funktionskomponenten.
import { useEffect, useState } from 'react';
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
// Runs when userId changes (componentDidMount + componentDidUpdate)
useEffect(() => {
let cancelled = false;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
if (!cancelled) setUser(data);
});
// Cleanup function (componentWillUnmount)
return () => { cancelled = true; };
}, [userId]); // dependency array
return <div>{user?.name}</div>;
}
// Run once on mount
useEffect(() => {
console.log('Component mounted');
return () => console.log('Component unmounted');
}, []); // empty dependency array
// Run on every render (rarely needed)
useEffect(() => {
console.log('Component rendered');
}); // no dependency arrayuseContext: Kontext konsumieren
useContext abonniert React-Kontext ohne verschachtelte Consumer-Komponenten.
import { createContext, useContext, useState } from 'react';
// 1. Create a context with a default value
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | null>(null);
// 2. Create a provider component
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. Consume context with useContext
function ThemeButton() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('Must be inside ThemeProvider');
return (
<button onClick={ctx.toggleTheme}>
Current: {ctx.theme}
</button>
);
}
// 4. Custom hook for cleaner usage
function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}useMemo: Teure Berechnungen memoisieren
useMemo cached das Ergebnis teurer Berechnungen zwischen Re-Renders.
import { useMemo, useState } from 'react';
function ExpensiveList({ items, filter }: { items: Item[]; filter: string }) {
// Only recalculates when items or filter changes
const filteredItems = useMemo(() => {
console.log('Filtering...');
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]);
// Memoize a sorted copy
const sortedItems = useMemo(() => {
return [...filteredItems].sort((a, b) => a.name.localeCompare(b.name));
}, [filteredItems]);
return (
<ul>
{sortedItems.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
// Do NOT overuse useMemo â only for truly expensive computations
// Simple operations do not need memoization
const total = useMemo(() => items.reduce((sum, i) => sum + i.price, 0), [items]);useCallback: Funktionen memoisieren
useCallback gibt eine memoisierte Version einer Callback-Funktion zuruck.
import { useCallback, useState, memo } from 'react';
// Child component wrapped in memo â only re-renders if props change
const SearchInput = memo(({ onSearch }: { onSearch: (q: string) => void }) => {
console.log('SearchInput rendered');
return <input onChange={e => onSearch(e.target.value)} />;
});
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
// Without useCallback, a new function is created every render
// causing SearchInput to re-render unnecessarily
const handleSearch = useCallback((q: string) => {
setQuery(q);
fetch(`/api/search?q=${q}`)
.then(res => res.json())
.then(setResults);
}, []); // stable reference
return (
<div>
<SearchInput onSearch={handleSearch} />
<ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
</div>
);
}useRef: Veranderliche Referenzen
useRef gibt ein veranderliches Ref-Objekt zuruck.
import { useRef, useEffect, useState } from 'react';
function TextInputWithFocus() {
// DOM reference
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus(); // auto-focus on mount
}, []);
return <input ref={inputRef} placeholder="Auto-focused" />;
}
function StopWatch() {
const [time, setTime] = useState(0);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const start = () => {
intervalRef.current = setInterval(() => {
setTime(t => t + 1);
}, 1000);
};
const stop = () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
// Track previous value
const prevTimeRef = useRef(time);
useEffect(() => { prevTimeRef.current = time; });
return (
<div>
<p>Time: {time}s (prev: {prevTimeRef.current}s)</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}useReducer: Komplexe State-Logik
useReducer ist eine Alternative zu useState fur komplexe State-Logik.
import { useReducer } from 'react';
interface State {
count: number;
step: number;
}
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset' }
| { type: 'setStep'; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'reset':
return { count: 0, step: 1 };
case 'setStep':
return { ...state, step: action.payload };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
return (
<div>
<p>Count: {state.count}</p>
<input
type="number"
value={state.step}
onChange={e => dispatch({ type: 'setStep', payload: Number(e.target.value) })}
/>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}Custom Hooks: Wiederverwendbare Logik
Custom Hooks extrahieren Komponentenlogik in wiederverwendbare Funktionen.
// useLocalStorage â persist state to localStorage
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
// useDebounce â debounce a rapidly changing value
function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
// useFetch â generic data fetching
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(url)
.then(res => res.json())
.then(data => { if (!cancelled) { setData(data); setLoading(false); } })
.catch(err => { if (!cancelled) { setError(err); setLoading(false); } });
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
// Usage
function App() {
const [name, setName] = useLocalStorage('name', '');
const debouncedName = useDebounce(name, 300);
const { data, loading } = useFetch<User[]>(`/api/search?q=${debouncedName}`);
}Regeln der Hooks
Zwei wesentliche Regeln fur Hooks.
- Hooks nur auf der obersten Ebene aufrufen.
- Hooks nur aus React-Funktionen aufrufen.
// WRONG â Hook inside a condition
function Bad({ isLoggedIn }) {
if (isLoggedIn) {
const [user, setUser] = useState(null); // breaks Hook order
}
}
// CORRECT â condition inside the Hook
function Good({ isLoggedIn }) {
const [user, setUser] = useState(null);
useEffect(() => {
if (isLoggedIn) fetchUser().then(setUser);
}, [isLoggedIn]);
}Haufige Fallstricke und Losungen
Veraltete Closures in useEffect
Wenn State in useEffect ohne korrekte Dependencies referenziert wird.
// BUG: stale closure
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
console.log(count); // always logs 0 (stale!)
setCount(count + 1); // always sets to 1
}, 1000);
return () => clearInterval(id);
}, []); // count is missing from dependencies
}
// FIX: use functional update
useEffect(() => {
const id = setInterval(() => {
setCount(prev => prev + 1); // always uses latest value
}, 1000);
return () => clearInterval(id);
}, []); // safe with functional updateEndlosschleifen mit useEffect
State in useEffect ohne korrekte Dependencies setzen verursacht Endlosschleifen.
// BUG: infinite loop
useEffect(() => {
setCount(count + 1); // triggers re-render, which runs effect again
}); // no dependency array = runs every render
// FIX: add dependency array
useEffect(() => {
if (count < 10) setCount(count + 1);
}, [count]); // only runs when count changesObjekt-/Array-Dependencies
Objekte und Arrays werden per Referenz verglichen.
// BUG: new object every render
function App() {
const options = { page: 1, limit: 10 }; // new ref each render
useEffect(() => {
fetchData(options);
}, [options]); // runs every render!
}
// FIX: useMemo to stabilize the reference
function App() {
const options = useMemo(() => ({ page: 1, limit: 10 }), []);
useEffect(() => {
fetchData(options);
}, [options]); // stable reference
}Haufig gestellte Fragen
Was sind React Hooks?
Funktionen, die React-Features in funktionalen Komponenten ermoglichen.
Unterschied zwischen useMemo und useCallback?
useMemo memoisiert einen Wert, useCallback memoisiert eine Funktion.
Wann useReducer statt useState?
Bei komplexer State-Logik oder mehreren Unterwerten.
Hooks in Klassenkomponenten?
Nein, nur in funktionalen Komponenten.
Endlosschleifen mit useEffect vermeiden?
Immer das korrekte Dependency-Array angeben.
React Hooks sind unverzichtbar fur moderne React-Entwicklung.