React stellt eine handvoll Hooks bereit, die auf den ersten Blick unabhängig erscheinen mögen. Doch bei näherer Betrachtung zeigt sich ein durchdachtes System: Jeder Hook hat eine klare Verantwortlichkeit, und viele ergänzen sich zu mächtigen Patterns. Diese Übersicht bietet die Vogelperspektive – eine Landkarte des Hook-Ökosystems, die zeigt, wo jeder Hook seinen Platz hat und wie sie zusammenspielen.
React’s Hooks lassen sich in sechs Hauptkategorien einteilen, die jeweils unterschiedliche Aspekte des Component-Lifecycles und der Anwendungsarchitektur adressieren.
State-Hooks verwalten Daten, die sich über die Zeit ändern können und deren Änderungen Re-Renderings auslösen. Sie sind das Herzstück reaktiver Komponenten.
| Hook | Zweck | Wann verwenden |
|---|---|---|
useState |
Einfacher, lokaler State | Einzelne Werte, Flags, primitive Datentypen |
useReducer |
Strukturierter State mit Logik | Komplexe State-Objekte, viele zusammenhängende Updates, State-Maschinen |
Die Wahl zwischen useState und useReducer
folgt der Komplexität. Ein Toggle-Flag oder ein Text-Input:
useState. Ein Formular mit Validierung und mehreren
abhängigen Feldern: useReducer. Die Faustregel: Wenn du
dich bei useState-Updates wiederholst oder mehr als drei
zusammenhängende State-Variablen hast, ist useReducer
wahrscheinlich besser.
// Einfach: useState
const [count, setCount] = useState(0);
// Komplex: useReducer
const [formState, dispatch] = useReducer(formReducer, initialState);
dispatch({ type: 'FIELD_CHANGED', field: 'email', value: 'user@example.com' });
Effect-Hooks ermöglichen Interaktionen, die außerhalb des reinen Rendering-Prozesses stattfinden – API-Calls, DOM-Manipulation, Subscriptions, Logging.
| Hook | Timing | Blockiert UI | Wann verwenden |
|---|---|---|---|
useEffect |
Nach Paint | Nein | Standard für Side Effects, async Operations, Subscriptions |
useLayoutEffect |
Vor Paint | Ja | DOM-Messungen, Layout-Korrekturen, die sofort sichtbar sein müssen |
Das Timing ist der entscheidende Unterschied. useEffect
ist asynchron und nicht-blockierend – ideal für alles, was nicht sofort
visuell relevant ist. useLayoutEffect ist synchron und
blockierend – nur für Operationen, die vor dem ersten Paint
abgeschlossen sein müssen, um visuelles Flackern zu vermeiden.
// Standard: useEffect für Daten-Fetching
useEffect(() => {
fetch('/api/data').then(setData);
}, []);
// Speziell: useLayoutEffect für Scroll-Restoration
useLayoutEffect(() => {
element.scrollTop = savedPosition;
}, []);
Ref-Hooks schaffen Persistenz zwischen Renderings, ohne Re-Renderings auszulösen. Sie sind die Brücke zwischen React’s deklarativem Modell und imperativem DOM-Zugriff.
| Hook | Zweck | Wann verwenden |
|---|---|---|
useRef |
Persistente Werte & DOM-Referenzen | Timer-IDs, DOM-Elemente, vorherige Werte, nicht-reaktive Daten |
useImperativeHandle |
Ref-API für Parent-Komponenten | Custom-Komponenten mit imperativem Interface (selten) |
useRef ist der vielseitigste Hook dieser Kategorie. Er
speichert Werte, die zwischen Renderings erhalten bleiben, aber keine
Re-Renderings auslösen. DOM-Referenzen sind der klassische Fall, aber
auch Timer-IDs, WebSocket-Connections oder Flags wie “isMounted” werden
oft in Refs gespeichert.
// DOM-Referenz
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();
// Persistenter Wert ohne Re-Rendering
const countRef = useRef(0);
countRef.current += 1; // Ändert sich, triggert aber kein Re-Rendering
Performance-Hooks memoizieren Werte oder Funktionen, um teure Berechnungen oder unnötige Re-Renderings zu vermeiden.
| Hook | Memoiziert | Wann verwenden |
|---|---|---|
useMemo |
Berechnete Werte | Teure Berechnungen, stabile Objekt-Referenzen für Child-Props |
useCallback |
Funktionen | Stabile Callback-Referenzen für memoizierte Child-Komponenten |
Beide basieren auf dem gleichen Prinzip: Caching mit Dependencies.
Der Unterschied liegt im Rückgabewert. useMemo führt eine
Funktion aus und cached das Ergebnis. useCallback cached
die Funktion selbst. Tatsächlich ist useCallback(fn, deps)
äquivalent zu useMemo(() => fn, deps).
// useMemo: Wert cachen
const sortedList = useMemo(() =>
items.sort((a, b) => a.price - b.price),
[items]
);
// useCallback: Funktion cachen
const handleClick = useCallback((id: number) => {
dispatch({ type: 'ITEM_CLICKED', id });
}, []);
Context-Hooks ermöglichen den Zugriff auf Daten, die über die Props-Hierarchie hinweg geteilt werden.
| Hook | Zweck | Wann verwenden |
|---|---|---|
useContext |
Context-Werte konsumieren | Theme, User, globale Settings – Daten, die viele Komponenten benötigen |
useContext löst das Prop-Drilling-Problem. Statt Daten
durch Dutzende Komponenten zu reichen, stellt ein Provider sie bereit,
und Consumer greifen direkt darauf zu. Kombiniert mit
useReducer entsteht ein “Mini-Redux”-Pattern für lokales
State-Management.
// Context definieren und bereitstellen
const ThemeContext = createContext<Theme>(defaultTheme);
function App() {
return (
<ThemeContext.Provider value={theme}>
<DeepNestedComponent />
</ThemeContext.Provider>
);
}
// Tief verschachtelt: direkter Zugriff
function DeepNestedComponent() {
const theme = useContext(ThemeContext);
return <div style={{ color: theme.textColor }}>Text</div>;
}
Diese Hooks adressieren spezifische Szenarien oder fortgeschrittene Performance-Optimierungen.
| Hook | Zweck | Verfügbarkeit |
|---|---|---|
useId |
Stabile IDs für Accessibility | React 18+ |
useTransition |
UI-Updates als “interruptible” markieren | React 18+ (Concurrent) |
useDeferredValue |
Werte mit niedriger Priorität | React 18+ (Concurrent) |
useSyncExternalStore |
Externe Stores subscriben | React 18+ |
useDebugValue |
Custom Hook debugging in DevTools | Immer (nur Dev) |
Diese Hooks sind entweder sehr spezifisch (useId für
Accessibility, useDebugValue für Entwicklung) oder Teil von
React’s Concurrent-Features (useTransition,
useDeferredValue), die komplexe Performance-Optimierungen
ermöglichen.
Die Beziehungen zwischen Hooks lassen sich visuell darstellen. Verschiedene Diagramme beleuchten unterschiedliche Aspekte – Hierarchie, häufige Kombinationen, Entscheidungswege.
Diese Mindmap zeigt die logische Gruppierung. Jede Hauptkategorie adressiert einen anderen Aspekt der Component-Entwicklung. State-Management ist zentral – fast jede Komponente benötigt es. Side Effects sind häufig. Performance-Hooks kommen hinzu, wenn Optimierung nötig wird.
Hooks werden selten isoliert verwendet. Bestimmte Kombinationen ergeben bewährte Patterns.
Diese Patterns erscheinen immer wieder:
Mini-Redux (useReducer +
useContext): State-Management ohne externe Library. Der
Reducer verwaltet Logik, Context macht ihn global verfügbar.
Memoizierte Callbacks (useCallback +
React.memo): Verhindert Re-Renderings in Child-Komponenten,
wenn Parent neu rendert, aber Props stabil bleiben.
Optimierte Berechnungen (useMemo +
Props): Teure Berechnungen werden gecached, Ergebnisse als Props
weitergegeben, Children profitieren von stabilen Referenzen.
Effects mit Cleanup: Subscriptions, Timer, Event-Listener – alles, was Setup und Teardown benötigt.
Layout-Korrekturen (useRef +
useLayoutEffect): DOM-Element referenzieren, messen,
korrigieren – alles vor dem Paint.
Async Data Loading (useState +
useEffect): Der Standard für Daten-Fetching. State hält
loading/data/error, Effect macht den Request.
Dieser Entscheidungsbaum führt durch die Hook-Auswahl basierend auf dem Problem, das gelöst werden soll. Nicht jedes Problem braucht einen Hook – manchmal ist eine einfache Variable oder direkte Berechnung die richtige Lösung.
Das wahrscheinlich häufigste Pattern kombiniert useState
für State-Management und useEffect für den async Fetch.
function DataLoader({ id }: { id: number }) {
const [data, setData] = useState<Data | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let ignore = false;
setLoading(true);
setError(null);
fetch(`/api/data/${id}`)
.then(res => res.json())
.then(data => {
if (!ignore) {
setData(data);
setLoading(false);
}
})
.catch(err => {
if (!ignore) {
setError(err.message);
setLoading(false);
}
});
return () => { ignore = true; };
}, [id]);
if (loading) return <Spinner />;
if (error) return <Error message={error} />;
return <Display data={data} />;
}
Drei State-Variablen tracken den Fetch-Zustand. Der Effect läuft bei
id-Änderungen. Das ignore-Flag verhindert Race
Conditions.
Für komplexere globale State-Verwaltung ohne externe Library.
// 1. State und Actions definieren
interface AppState {
user: User | null;
theme: Theme;
}
type AppAction =
| { type: 'USER_LOGIN'; user: User }
| { type: 'USER_LOGOUT' }
| { type: 'THEME_TOGGLE' };
function appReducer(state: AppState, action: AppAction): AppState {
// Reducer-Logik
}
// 2. Contexts erstellen
const StateContext = createContext<AppState | undefined>(undefined);
const DispatchContext = createContext<Dispatch<AppAction> | undefined>(undefined);
// 3. Provider
function AppProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
// 4. Custom Hooks
function useAppState() {
const context = useContext(StateContext);
if (!context) throw new Error('Outside Provider');
return context;
}
function useAppDispatch() {
const context = useContext(DispatchContext);
if (!context) throw new Error('Outside Provider');
return context;
}
Dieser Pattern bietet Redux-ähnliche Struktur mit React-Bordmitteln. State und Dispatch in separaten Contexts vermeiden unnötige Re-Renderings.
Für Listen mit vielen Items, wo jedes Item Callbacks erhält.
function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
// Stabile Callback-Referenz
const handleToggle = useCallback((id: number) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
}, []);
// Gefilterte Liste memoiziert
const activeTodos = useMemo(() =>
todos.filter(todo => !todo.done),
[todos]
);
return (
<ul>
{activeTodos.map(todo => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
))}
</ul>
);
}
const TodoItem = React.memo(({ todo, onToggle }: Props) => (
<li onClick={() => onToggle(todo.id)}>
{todo.text}
</li>
));
useCallback stabilisiert die Callback-Funktion.
useMemo cached die gefilterte Liste.
React.memo verhindert Re-Renderings von Items, die sich
nicht geändert haben.
Für Komponenten, die DOM-Messungen benötigen und darauf basierend Anpassungen vornehmen.
function AdaptiveTooltip({ children, content }: Props) {
const triggerRef = useRef<HTMLDivElement>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState({ top: 0, left: 0 });
const [isVisible, setIsVisible] = useState(false);
useLayoutEffect(() => {
if (!isVisible || !triggerRef.current || !tooltipRef.current) return;
const triggerRect = triggerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
let top = triggerRect.bottom + 8;
let left = triggerRect.left;
// Viewport-Kollision vermeiden
if (left + tooltipRect.width > window.innerWidth) {
left = window.innerWidth - tooltipRect.width - 8;
}
if (top + tooltipRect.height > window.innerHeight) {
top = triggerRect.top - tooltipRect.height - 8;
}
setPosition({ top, left });
}, [isVisible]);
return (
<>
<div ref={triggerRef} onMouseEnter={() => setIsVisible(true)}>
{children}
</div>
{isVisible && (
<div
ref={tooltipRef}
style={{ position: 'fixed', ...position }}
>
{content}
</div>
)}
</>
);
}
useRef für DOM-Referenzen, useLayoutEffect
für Messungen vor dem Paint, useState für die berechnete
Position.
React’s Hook-Regeln sind nicht willkürlich. Sie resultieren aus der internen Implementierung.
Regel 1: Nur auf oberster Ebene aufrufen
Hooks dürfen nicht in Schleifen, Bedingungen oder verschachtelten Funktionen aufgerufen werden. React verlässt sich auf die Aufruf-Reihenfolge, um Hook-State zu tracken. Wenn die Reihenfolge zwischen Renderings variiert, gerät das Tracking durcheinander.
// Falsch: Bedingter Hook-Aufruf
function Component({ condition }: Props) {
if (condition) {
const [value, setValue] = useState(0); // ❌ Hook in Bedingung
}
}
// Richtig: Hook immer, Bedingung im Effect
function Component({ condition }: Props) {
const [value, setValue] = useState(0); // ✓ Hook auf oberster Ebene
useEffect(() => {
if (condition) {
// Bedingte Logik im Effect
}
}, [condition]);
}
Regel 2: Nur in React-Funktionen aufrufen
Hooks funktionieren nur in Funktionskomponenten oder Custom Hooks. Sie benötigen React’s Kontext – die Component-Fiber, den Rendering-Lifecycle. Außerhalb dieses Kontexts haben sie keine Bedeutung.
// Falsch: Hook in regulärer Funktion
function helperFunction() {
const [value, setValue] = useState(0); // ❌ Kein React-Kontext
return value;
}
// Richtig: Hook in Custom Hook
function useCustomLogic() {
const [value, setValue] = useState(0); // ✓ Custom Hook
return value;
}
Custom Hooks sind Funktionen, die andere Hooks verwenden und wiederverwendbare Logik kapseln. Der Name muss mit “use” beginnen – eine Konvention, die React nutzt, um Hook-Regeln zu validieren.
// Custom Hook für Window-Size-Tracking
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
function handleResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// Verwendung
function Component() {
const { width, height } = useWindowSize();
return <div>Window: {width} × {height}</div>;
}
Custom Hooks können beliebig komplex sein und multiple Hooks kombinieren. Sie sind der Mechanismus für Code-Reuse in React.
Hooks sind React’s Antwort auf Component-Logik-Reuse und State-Management. Sie ersetzen Class-Components komplett und bieten ein kompositionsfähiges, funktionales Modell.
Die Kategorisierung hilft beim Verständnis: State-Hooks für Daten, Effect-Hooks für Side Effects, Ref-Hooks für Persistenz ohne Reaktivität, Performance-Hooks für Optimierung. Jeder Hook hat einen klaren Zweck, aber die wahre Kraft liegt in ihrer Kombination.
Die Patterns – Mini-Redux, memoizierte Listen, async Data Loading – erscheinen in jeder größeren React-Anwendung. Sie zu kennen und anzuwenden, macht den Unterschied zwischen Code, der funktioniert, und Code, der skaliert.
Diese Übersicht ist kein Ersatz für das detaillierte Verständnis jedes einzelnen Hooks – die vorherigen Kapitel haben das geliefert. Aber sie ist die Landkarte, die zeigt, wie alles zusammenpasst. Ein Referenzpunkt, um den richtigen Hook für das vorliegende Problem zu finden, und um zu verstehen, wie Hooks zusammenwirken, um robuste, performante React-Anwendungen zu schaffen.