React Hooks - Praktischer Leitfaden von Grundlagen bis Fortgeschritten
React Hooks Praktischer
poradnikiReact Hooks - Ein Praktischer Leitfaden von Grund auf
React Hooks haben die Art und Weise, wie wir Komponenten in React erstellen, grundlegend veraendert. Seit ihrer Einfuehrung in Version 16.8 sind Hooks zum bevorzugten Ansatz fuer die Verwaltung von State und Seiteneffekten geworden und haben Klassenkomponenten weitgehend abgeloest. In diesem Leitfaden behandeln wir alle wichtigen Hooks, zeigen praktische Muster mit TypeScript und stellen die neuesten Ergaenzungen aus React 19 vor.
Was sind React Hooks?#
Hooks sind Funktionen, die es ermoeglichen, sich in die internen Mechanismen von React aus funktionalen Komponenten heraus "einzuklinken". Sie erlauben die Nutzung von State, Seiteneffekten, Context und vielen weiteren Funktionen, ohne Klassenkomponenten schreiben zu muessen.
// Vor Hooks - Klassenkomponente
class Counter extends React.Component<{}, { count: number }> {
state = { count: 0 };
render() {
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Klicks: {this.state.count}
</button>
);
}
}
// Mit Hooks - Funktionale Komponente
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Klicks: {count}
</button>
);
}
useState - Lokalen State verwalten#
useState ist der grundlegendste Hook. Er fuegt einer funktionalen Komponente State hinzu. Er akzeptiert einen Initialwert und gibt ein Paar zurueck: den aktuellen State-Wert und eine Funktion zu dessen Aktualisierung.
import { useState } from 'react';
interface FormData {
name: string;
email: string;
age: number;
}
function RegistrationForm() {
const [formData, setFormData] = useState<FormData>({
name: '',
email: '',
age: 0,
});
const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (field: keyof FormData, value: string | number) => {
setFormData(prev => ({ ...prev, [field]: value }));
// Fehler beim Aendern des Wertes loeschen
setErrors(prev => ({ ...prev, [field]: undefined }));
};
const validate = (): boolean => {
const newErrors: Partial<Record<keyof FormData, string>> = {};
if (!formData.name.trim()) {
newErrors.name = 'Name ist erforderlich';
}
if (!formData.email.includes('@')) {
newErrors.email = 'Ungueltige E-Mail-Adresse';
}
if (formData.age < 18) {
newErrors.age = 'Sie muessen mindestens 18 Jahre alt sein';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validate()) return;
setIsSubmitting(true);
try {
await submitForm(formData);
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={e => handleChange('name', e.target.value)}
placeholder="Name"
/>
{errors.name && <span className="error">{errors.name}</span>}
<input
value={formData.email}
onChange={e => handleChange('email', e.target.value)}
placeholder="E-Mail"
/>
{errors.email && <span className="error">{errors.email}</span>}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Wird gesendet...' : 'Registrieren'}
</button>
</form>
);
}
Lazy State-Initialisierung#
Wenn der Initialwert aufwendige Berechnungen erfordert, uebergeben Sie eine Funktion anstelle eines Wertes:
// Teure Initialisierung - wird bei JEDEM Render ausgefuehrt
const [items, setItems] = useState(parseExpensiveData(rawData));
// Lazy Initialisierung - wird NUR einmal ausgefuehrt
const [items, setItems] = useState(() => parseExpensiveData(rawData));
State basierend auf dem vorherigen Wert aktualisieren#
Verwenden Sie immer die funktionale Form, wenn der neue Wert vom vorherigen abhaengt:
// Potenzielles Problem mit Batched Updates
const handleDoubleIncrement = () => {
setCount(count + 1); // count = 0, setzt auf 1
setCount(count + 1); // count ist immer noch 0, setzt auf 1
};
// Korrekter Ansatz
const handleDoubleIncrement = () => {
setCount(prev => prev + 1); // 0 -> 1
setCount(prev => prev + 1); // 1 -> 2
};
useEffect - Seiteneffekte#
useEffect ermoeglicht die Synchronisation Ihrer Komponente mit externen Systemen: APIs, Abonnements, Timern oder dem DOM.
import { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
email: string;
}
function UserProfile({ userId }: { userId: number }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
async function fetchUser() {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data: User = await response.json();
setUser(data);
} catch (err) {
if (err instanceof Error && err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
}
fetchUser();
// Cleanup - Anfrage bei Unmount oder userId-Aenderung abbrechen
return () => controller.abort();
}, [userId]);
if (loading) return <div>Wird geladen...</div>;
if (error) return <div>Fehler: {error}</div>;
if (!user) return null;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
Typische useEffect-Muster#
// Einmal nach dem Mounten ausfuehren
useEffect(() => {
initializeAnalytics();
}, []);
// Auf Aenderungen der Abhaengigkeiten reagieren
useEffect(() => {
document.title = `Sie haben ${count} neue Nachrichten`;
}, [count]);
// Abonnement mit Cleanup
useEffect(() => {
const ws = new WebSocket('wss://api.example.com/feed');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setMessages(prev => [...prev, data]);
};
return () => ws.close();
}, []);
// Event Listener
useEffect(() => {
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
useContext - Globaler State ohne Prop Drilling#
useContext beseitigt das Problem der Weitergabe von Props durch viele Komponentenebenen.
import { createContext, useContext, useState, ReactNode } from 'react';
interface Theme {
mode: 'light' | 'dark';
primaryColor: string;
}
interface ThemeContextType {
theme: Theme;
toggleMode: () => void;
setPrimaryColor: (color: string) => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// Custom Hook mit Validierung
function useTheme(): ThemeContextType {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme muss innerhalb eines ThemeProvider verwendet werden');
}
return context;
}
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>({
mode: 'light',
primaryColor: '#3b82f6',
});
const toggleMode = () => {
setTheme(prev => ({
...prev,
mode: prev.mode === 'light' ? 'dark' : 'light',
}));
};
const setPrimaryColor = (color: string) => {
setTheme(prev => ({ ...prev, primaryColor: color }));
};
return (
<ThemeContext.Provider value={{ theme, toggleMode, setPrimaryColor }}>
{children}
</ThemeContext.Provider>
);
}
// Komponente, die den Context nutzt
function ThemeToggle() {
const { theme, toggleMode } = useTheme();
return (
<button onClick={toggleMode}>
Aktuelles Theme: {theme.mode === 'light' ? 'Hell' : 'Dunkel'}
</button>
);
}
useReducer - Erweiterte State-Verwaltung#
useReducer ist die bessere Wahl gegenueber useState, wenn die State-Logik komplex ist oder mehrere zusammenhaengende Werte umfasst.
import { useReducer } from 'react';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartState {
items: CartItem[];
discount: number;
isCheckingOut: boolean;
}
type CartAction =
| { type: 'ADD_ITEM'; payload: Omit<CartItem, 'quantity'> }
| { type: 'REMOVE_ITEM'; payload: { id: string } }
| { type: 'UPDATE_QUANTITY'; payload: { id: string; quantity: number } }
| { type: 'APPLY_DISCOUNT'; payload: { discount: number } }
| { type: 'CHECKOUT_START' }
| { type: 'CHECKOUT_COMPLETE' }
| { type: 'CLEAR_CART' };
function cartReducer(state: CartState, action: CartAction): CartState {
switch (action.type) {
case 'ADD_ITEM': {
const existingItem = state.items.find(item => item.id === action.payload.id);
if (existingItem) {
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: item.quantity + 1 }
: item
),
};
}
return {
...state,
items: [...state.items, { ...action.payload, quantity: 1 }],
};
}
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload.id),
};
case 'UPDATE_QUANTITY':
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: Math.max(0, action.payload.quantity) }
: item
).filter(item => item.quantity > 0),
};
case 'APPLY_DISCOUNT':
return { ...state, discount: action.payload.discount };
case 'CHECKOUT_START':
return { ...state, isCheckingOut: true };
case 'CHECKOUT_COMPLETE':
return { items: [], discount: 0, isCheckingOut: false };
case 'CLEAR_CART':
return { items: [], discount: 0, isCheckingOut: false };
default:
return state;
}
}
function ShoppingCart() {
const [cart, dispatch] = useReducer(cartReducer, {
items: [],
discount: 0,
isCheckingOut: false,
});
const total = cart.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
const discountedTotal = total * (1 - cart.discount);
return (
<div>
{cart.items.map(item => (
<div key={item.id}>
<span>{item.name} - {item.price} EUR x {item.quantity}</span>
<button onClick={() => dispatch({
type: 'UPDATE_QUANTITY',
payload: { id: item.id, quantity: item.quantity + 1 }
})}>+</button>
<button onClick={() => dispatch({
type: 'UPDATE_QUANTITY',
payload: { id: item.id, quantity: item.quantity - 1 }
})}>-</button>
<button onClick={() => dispatch({
type: 'REMOVE_ITEM',
payload: { id: item.id }
})}>Entfernen</button>
</div>
))}
<p>Gesamt: {discountedTotal.toFixed(2)} EUR</p>
</div>
);
}
useMemo und useCallback - Leistungsoptimierung#
Diese Hooks helfen dabei, unnoetige Berechnungen und Re-Renders zu vermeiden.
useMemo - Werte memoisieren#
import { useMemo, useState } from 'react';
interface Product {
id: string;
name: string;
price: number;
category: string;
rating: number;
}
function ProductList({ products }: { products: Product[] }) {
const [sortBy, setSortBy] = useState<'price' | 'rating'>('price');
const [filterCategory, setFilterCategory] = useState<string>('all');
const [searchQuery, setSearchQuery] = useState('');
// Aufwendiges Filtern und Sortieren memoisieren
const filteredAndSorted = useMemo(() => {
let result = products;
// Nach Kategorie filtern
if (filterCategory !== 'all') {
result = result.filter(p => p.category === filterCategory);
}
// Nach Suchbegriff filtern
if (searchQuery) {
const query = searchQuery.toLowerCase();
result = result.filter(p =>
p.name.toLowerCase().includes(query)
);
}
// Sortieren
return [...result].sort((a, b) =>
sortBy === 'price' ? a.price - b.price : b.rating - a.rating
);
}, [products, sortBy, filterCategory, searchQuery]);
// Statistiken memoisieren
const stats = useMemo(() => ({
count: filteredAndSorted.length,
avgPrice: filteredAndSorted.reduce((s, p) => s + p.price, 0) / filteredAndSorted.length || 0,
avgRating: filteredAndSorted.reduce((s, p) => s + p.rating, 0) / filteredAndSorted.length || 0,
}), [filteredAndSorted]);
return (
<div>
<input
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
placeholder="Produkte suchen..."
/>
<p>Gefunden: {stats.count} | Durchschnittspreis: {stats.avgPrice.toFixed(2)} EUR</p>
{filteredAndSorted.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
useCallback - Funktionen memoisieren#
import { useCallback, memo } from 'react';
// Kind-Komponente mit React.memo
const TodoItem = memo(function TodoItem({
todo,
onToggle,
onDelete,
}: {
todo: { id: string; text: string; done: boolean };
onToggle: (id: string) => void;
onDelete: (id: string) => void;
}) {
console.log(`Render TodoItem: ${todo.text}`);
return (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Loeschen</button>
</li>
);
});
function TodoList() {
const [todos, setTodos] = useState<{ id: string; text: string; done: boolean }[]>([]);
const [input, setInput] = useState('');
// Ohne useCallback - neue Referenz bei jedem Render
// = jedes TodoItem wird neu gerendert
const handleToggle = useCallback((id: string) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
}, []);
const handleDelete = useCallback((id: string) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
return (
<div>
<input value={input} onChange={e => setInput(e.target.value)} />
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</ul>
</div>
);
}
useRef - Referenzen und veraenderliche Werte#
useRef speichert einen veraenderlichen Wert, der bei Aenderung kein Re-Render ausloest. Er ist ideal zum Speichern von DOM-Element-Referenzen und persistenten Werten zwischen Renders.
import { useRef, useEffect, useState } from 'react';
function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement>(null);
const renderCount = useRef(0);
const previousValue = useRef<string>('');
const [value, setValue] = useState('');
// Render-Zaehler (verursacht keine zusaetzlichen Renders)
renderCount.current += 1;
useEffect(() => {
// Auto-Focus beim Mounten
inputRef.current?.focus();
}, []);
useEffect(() => {
previousValue.current = value;
}, [value]);
return (
<div>
<input
ref={inputRef}
value={value}
onChange={e => setValue(e.target.value)}
placeholder="Dieses Feld erhaelt Auto-Focus"
/>
<p>Anzahl der Renders: {renderCount.current}</p>
<p>Vorheriger Wert: {previousValue.current}</p>
</div>
);
}
useRef mit Timern#
function Stopwatch() {
const [time, setTime] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const start = () => {
if (isRunning) return;
setIsRunning(true);
intervalRef.current = setInterval(() => {
setTime(prev => prev + 10);
}, 10);
};
const stop = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
setIsRunning(false);
};
const reset = () => {
stop();
setTime(0);
};
// Cleanup beim Unmounten
useEffect(() => {
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, []);
const formatTime = (ms: number) => {
const minutes = Math.floor(ms / 60000);
const seconds = Math.floor((ms % 60000) / 1000);
const centiseconds = Math.floor((ms % 1000) / 10);
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`;
};
return (
<div>
<span>{formatTime(time)}</span>
<button onClick={start} disabled={isRunning}>Start</button>
<button onClick={stop} disabled={!isRunning}>Stopp</button>
<button onClick={reset}>Zuruecksetzen</button>
</div>
);
}
Custom Hooks - Eigene Hooks erstellen#
Custom Hooks sind ein leistungsfaehiger Mechanismus zur Extraktion und Wiederverwendung von State-Logik ueber Komponenten hinweg.
// useLocalStorage - persistenter State in localStorage
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setValue = (value: T | ((prev: T) => T)) => {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
};
return [storedValue, setValue];
}
// useDebounce - verzoegerter Wert
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// useFetch - generischer Hook zum Datenabruf
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
setLoading(true);
setError(null);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const json: T = await response.json();
setData(json);
} catch (err) {
if (err instanceof Error && err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
}
fetchData();
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
// Verwendung
function SearchPage() {
const [query, setQuery] = useLocalStorage('searchQuery', '');
const debouncedQuery = useDebounce(query, 300);
const { data, loading, error } = useFetch<Product[]>(
`/api/search?q=${encodeURIComponent(debouncedQuery)}`
);
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
{loading && <p>Wird geladen...</p>}
{error && <p>Fehler: {error}</p>}
{data?.map(product => <ProductCard key={product.id} product={product} />)}
</div>
);
}
Regeln der Hooks (Rules of Hooks)#
React verlangt die Einhaltung zweier grundlegender Regeln:
1. Hooks nur auf der obersten Ebene aufrufen#
// Verwenden Sie Hooks niemals in Bedingungen, Schleifen oder verschachtelten Funktionen
function BadComponent({ isLoggedIn }: { isLoggedIn: boolean }) {
// NIEMALS so machen
if (isLoggedIn) {
const [user, setUser] = useState(null); // Schlechte Praxis
}
// Korrekt - Hook immer auf der obersten Ebene
const [user, setUser] = useState(null);
// Bedingung innerhalb oder nach dem Hook
useEffect(() => {
if (isLoggedIn) {
fetchUser().then(setUser);
}
}, [isLoggedIn]);
}
2. Hooks nur aus React-Komponenten oder Custom Hooks aufrufen#
// Rufen Sie Hooks nicht in regulaeren JavaScript-Funktionen auf
function regularFunction() {
const [state, setState] = useState(0); // Fehler
}
// Korrekt - Custom Hook (Name beginnt mit "use")
function useCounter(initial: number = 0) {
const [count, setCount] = useState(initial);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
return { count, increment, decrement };
}
Neuheiten in React 19#
React 19 fuehrt neue Hooks ein, die gaengige Muster erheblich vereinfachen.
useFormStatus#
Verfolgt den Formular-Status ohne Props weiterzugeben:
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending, data, method } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Wird gesendet...' : 'Formular absenden'}
</button>
);
}
function ContactForm() {
async function handleSubmit(formData: FormData) {
'use server';
const name = formData.get('name');
const email = formData.get('email');
await saveContact({ name, email });
}
return (
<form action={handleSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="E-Mail" required />
<SubmitButton />
</form>
);
}
useOptimistic#
Ermoeglicht optimistische UI-Aktualisierungen - zeigt das erwartete Ergebnis an, bevor der Server die Operation bestaetigt:
import { useOptimistic, useState } from 'react';
interface Message {
id: string;
text: string;
sending?: boolean;
}
function Chat() {
const [messages, setMessages] = useState<Message[]>([]);
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state: Message[], newMessage: Message) => [
...state,
{ ...newMessage, sending: true },
]
);
async function sendMessage(formData: FormData) {
const text = formData.get('message') as string;
const optimisticMsg: Message = {
id: crypto.randomUUID(),
text,
sending: true,
};
addOptimisticMessage(optimisticMsg);
const savedMessage = await saveMessageToServer(text);
setMessages(prev => [...prev, savedMessage]);
}
return (
<div>
{optimisticMessages.map(msg => (
<div key={msg.id} style={{ opacity: msg.sending ? 0.6 : 1 }}>
{msg.text}
{msg.sending && <span> (wird gesendet...)</span>}
</div>
))}
<form action={sendMessage}>
<input name="message" placeholder="Nachricht eingeben..." />
<button type="submit">Senden</button>
</form>
</div>
);
}
use - Eine neue Art, Ressourcen zu lesen#
Der use-Hook ermoeglicht das Lesen von Werten aus Promises und Context auf eine Weise, die kein anderer Hook kann - sogar bedingt:
import { use, Suspense } from 'react';
// Promise lesen
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// Bedingtes Context-Lesen
function StatusMessage({ isAdmin }: { isAdmin: boolean }) {
if (isAdmin) {
const adminContext = use(AdminContext);
return <p>Admin-Panel: {adminContext.dashboardUrl}</p>;
}
return <p>Benutzeransicht</p>;
}
// Verwendung mit Suspense
function App() {
const userPromise = fetchUser(1);
return (
<Suspense fallback={<div>Profil wird geladen...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
Leistungsmuster#
Unnoetige Re-Renders vermeiden#
import { memo, useMemo, useCallback } from 'react';
// 1. Komponenten aufteilen - State isolieren
function SearchPage() {
return (
<div>
<SearchInput /> {/* Such-State lebt hier */}
<ExpensiveStaticContent /> {/* Wird nicht neu gerendert */}
</div>
);
}
// 2. Kompositionsmuster - Children werden nicht neu gerendert
function Layout({ children }: { children: ReactNode }) {
const [sidebarOpen, setSidebarOpen] = useState(false);
return (
<div>
<Sidebar open={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} />
<main>{children}</main> {/* Children werden nicht neu gerendert */}
</div>
);
}
// 3. Context-Memoisierung
function OptimizedProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const value = useMemo(
() => ({ user, setUser }),
[user]
);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
}
Haeufige Fehler#
1. Fehlende Abhaengigkeiten in useEffect#
// Problem - Stale Closure
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // Liest immer count = 0
}, 1000);
return () => clearInterval(id);
}, []); // count fehlt in den Abhaengigkeiten
// Loesung - funktionale Form verwenden
useEffect(() => {
const id = setInterval(() => {
setCount(prev => prev + 1); // Immer aktueller Wert
}, 1000);
return () => clearInterval(id);
}, []);
}
2. Endlosschleife in useEffect#
// Problem - Objekt als Abhaengigkeit
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const filters = { role: 'admin', active: true }; // Neues Objekt bei jedem Render
useEffect(() => {
fetchUsers(filters).then(setUsers); // Endlosschleife
}, [filters]); // filters ist immer eine neue Referenz
// Loesung - useMemo
const filters = useMemo(() => ({ role: 'admin', active: true }), []);
useEffect(() => {
fetchUsers(filters).then(setUsers);
}, [filters]);
}
3. Uebermaeessiger Einsatz von useEffect#
// Anti-Pattern - useEffect fuer Datentransformation
function FilteredList({ items, filter }: Props) {
const [filtered, setFiltered] = useState(items);
useEffect(() => {
setFiltered(items.filter(item => item.category === filter));
}, [items, filter]);
// Korrekt - waehrend des Renderns berechnen
const filtered = useMemo(
() => items.filter(item => item.category === filter),
[items, filter]
);
}
Zusammenfassung#
React Hooks sind das Fundament des modernen React:
- useState - lokaler Komponentenstate
- useEffect - Synchronisation mit externen Systemen
- useContext - globaler State ohne Prop Drilling
- useReducer - komplexe State-Logik
- useMemo / useCallback - Leistungsoptimierung
- useRef - DOM-Referenzen und veraenderliche Werte
- Custom Hooks - wiederverwendbare State-Logik
- React 19 - useFormStatus, useOptimistic, use
Der Schluessel liegt darin, zu verstehen, wann und warum jeder Hook verwendet werden sollte, anstatt sie blind anzuwenden. Beachten Sie die Regeln der Hooks und messen Sie die Leistung, bevor Sie optimieren.
Brauchen Sie Unterstuetzung?#
Bei MDS Software Solutions Group helfen wir Ihnen bei:
- Entwicklung moderner React-Anwendungen mit TypeScript
- Migration von Klassenkomponenten zu Hooks
- Leistungsoptimierung von Frontend-Anwendungen
- Implementierung von Best Practices und Architekturmustern
- Code-Reviews und Qualitaetsaudits
Kontaktieren Sie uns, um Ihr Projekt zu besprechen!
Team von Programmierexperten, die sich auf moderne Webtechnologien spezialisiert haben.