Hooks sind spezielle Funktionen, mit denen Function-Components Features bekommen, die früher Klassen-Components vorbehalten waren: lokaler State, Side-Effects, Refs, Context, Performance-Optimierungen. Sie wurden 2018 mit React 16.8 eingeführt und haben Klassen-Components in praktisch allen neuen Codebases verdrängt. Hooks beginnen per Konvention mit use (useState, useEffect, useRef) und folgen zwei harten Hook-Regeln: nur auf der Top-Ebene einer Komponente aufrufen, nie in Schleifen oder Bedingungen — und nur in React-Components oder anderen Hooks, nie in normalen Funktionen.
Was sind Hooks?
Ein Hook ist eine Funktion, die React-interne Mechanismen nutzt — typischerweise um etwas zwischen Renders zu speichern oder Side-Effects auszulösen. Die Komponente selbst läuft bei jedem Render neu, aber der Hook „erinnert" sich an seinen Zustand.
import { useState, useEffect } from 'react';
const Counter = () => {
const [count, setCount] = useState(0); // State-Hook
useEffect(() => { // Side-Effect-Hook
document.title = `Klicks: ${count}`;
}, [count]);
return (
<button onClick={() => setCount(c => c + 1)}>
Klicks: {count}
</button>
);
};useState speichert count über Re-Renders hinweg. useEffect führt nach jedem Render mit geändertem count einen Side-Effect aus. Beides funktioniert nur, weil React sich pro Komponente merkt, welcher Hook welchen Wert hat.
Die zwei Hook-Regeln
Regel 1: Nur auf der Top-Ebene
Hooks müssen immer in derselben Reihenfolge und Anzahl aufgerufen werden. Dadurch kann React sie intern korrekt zuordnen.
// FALSCH — bedingter Hook-Aufruf
const Broken = ({ enabled }) => {
if (enabled) {
const [count, setCount] = useState(0); // FEHLER
}
return <p>Hi</p>;
};
// FALSCH — Hook in Schleife
const BrokenLoop = ({ items }) => {
for (const item of items) {
const [x, setX] = useState(0); // FEHLER
}
};
// RICHTIG — Top-Level, immer aufgerufen
const Fixed = ({ enabled }) => {
const [count, setCount] = useState(0);
if (!enabled) return null;
return <p>{count}</p>;
};Daher sind Hooks niemals in if, for, while, try/catch oder nach einem Early-Return erlaubt. Bedingte Logik gehört INNERHALB des Hook-Callbacks oder als Verzweigung im JSX.
Regel 2: Nur in React-Funktionen
Hooks dürfen aufgerufen werden in:
- React-Function-Components (mit PascalCase-Namen)
- Custom Hooks (Funktionen, deren Name mit
usebeginnt)
Nicht erlaubt in: normalen Funktionen, Klassen-Components, Event-Handlern, Promise-Callbacks, asynchronen Funktionen.
// FALSCH — kein Component, kein Custom Hook
function helper() {
const [x] = useState(0); // FEHLER
}
// RICHTIG — Custom Hook (Name beginnt mit `use`)
function useCounter(start = 0) {
const [count, setCount] = useState(start);
return [count, () => setCount(c => c + 1)];
}
// RICHTIG — Function-Component
function App() {
const [count, inc] = useCounter(0);
return <button onClick={inc}>{count}</button>;
}Der Linter eslint-plugin-react-hooks mit der Regel rules-of-hooks erkennt Verstöße zuverlässig.
Eingebaute Hooks im Überblick
| Hook | Zweck |
|---|---|
useState | Lokaler veränderbarer State pro Komponente |
useEffect | Side-Effects nach dem Render (Fetch, Subscribe, DOM) |
useLayoutEffect | Wie useEffect, aber synchron vor dem Browser-Paint |
useRef | Mutable Container für DOM-Knoten oder Werte ohne Re-Render |
useMemo | Wert memoisieren, nur neu berechnen bei Dependency-Änderung |
useCallback | Funktion memoisieren, stabile Referenz für React.memo-Kinder |
useContext | Context-Wert lesen ohne Prop-Drilling |
useReducer | Komplexen State mit Actions verwalten (Redux-Style local) |
useImperativeHandle | Selektives API für Parent-Components via ref |
useTransition | Updates als „nicht dringend" markieren (Concurrent) |
useDeferredValue | Wert mit Verzögerung übernehmen (verhindert UI-Stockungen) |
useId | Eindeutige IDs für a11y-Attribute (SSR-kompatibel) |
useSyncExternalStore | Externe Stores (Redux, Zustand) sicher subscriben |
useOptimistic | Optimistische UI-Updates (React 19) |
useActionState | Form-Actions mit State-Update (React 19) |
Dazu kommen die selteneren useDebugValue (für React DevTools) und useInsertionEffect (für CSS-in-JS-Libraries).
Custom Hooks
Eigene Hooks sind Funktionen, die andere Hooks aufrufen und wiederverwendbare Logik bündeln. Konvention: der Name beginnt mit use.
import { useState, useEffect } from 'react';
function useLocalStorage(key, initial) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initial;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Verwendung
const Theme = () => {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>{theme}</button>;
};Custom Hooks haben die gleichen Regeln wie eingebaute — Top-Level, in Components oder anderen Hooks. Der use-Prefix ist nicht nur Konvention, sondern wird vom Linter zur Hook-Erkennung genutzt.
Hooks vs. Class-Lifecycles
| Class-Lifecycle | Hook-Äquivalent |
|---|---|
componentDidMount | useEffect(..., []) |
componentDidUpdate | useEffect(..., [deps]) |
componentWillUnmount | Return-Funktion in useEffect |
getDerivedStateFromProps | meist überflüssig — einfach im Render rechnen |
shouldComponentUpdate | React.memo + useMemo/useCallback |
this.state / setState | useState / useReducer |
Der wichtigste Unterschied: Class-Lifecycles waren zeitlich organisiert (mount, update, unmount). Hooks sind konzeptuell organisiert (State, Effect, Memo) — ein Effect kann sowohl beim Mount als auch bei jedem Update laufen, abhängig vom Dependency-Array.
Interessantes
Hooks beginnen mit use — das ist Pflicht für den Linter.
Der ESLint-Linter eslint-plugin-react-hooks erkennt Hooks am use-Prefix. Eigene Funktionen, die Hooks intern nutzen, MÜSSEN ebenfalls mit use beginnen — sonst warnt der Linter nicht bei Verstößen gegen Hook-Regeln.
Hook-Reihenfolge ist heilig — gleicher Aufruf, gleicher Index.
React identifiziert Hook-Werte intern per Aufruf-Reihenfolge, nicht per Name. Wenn die Reihenfolge zwischen Renders wechselt (durch bedingte Aufrufe), gibt es Daten-Korruption. Daher: Hooks IMMER am Anfang, IMMER vollständig, IMMER in gleicher Reihenfolge.
Hooks in Production-Builds laufen genauso wie in Development.
Anders als manche andere React-Features (StrictMode-Double-Render, dev-only Warnings) verhalten sich Hooks in Production identisch. Was im Dev funktioniert, funktioniert auch im Build — und umgekehrt.
Hooks sind die Antwort auf 'wie wiederverwendet man Logik?'
Vor Hooks gab es Render Props, HOCs, Mixins — alle mit ihren eigenen Problemen (Wrapper-Hell, Prop-Collisions, ungenauer this-Kontext). Custom Hooks lösen das elegant: Logik als Funktion, ohne Komponenten-Verschachtelung.
Hooks in async-Funktionen oder Callbacks — nicht erlaubt.
setTimeout(() => useState(0)) oder async function() { useState(0) } verletzen die Hook-Regeln. Hooks müssen synchron im Komponenten-Body laufen. Wer State asynchron setzen will: synchron useState aufrufen, im Effect/Handler den Setter aufrufen.
React DevTools zeigen alle Hooks einer Komponente.
Im React-DevTools-Panel ist pro Komponente sichtbar, welche Hooks aufgerufen wurden und welche Werte sie halten. Hilfreich beim Debuggen — vor allem bei verschachtelten Custom Hooks.
Class-Components funktionieren weiter, aber neue Features sind hook-only.
Suspense für Daten-Laden, useTransition, useOptimistic, Server-Components — alle nur in Function-Components nutzbar. Wer in Class-Code arbeitet, ist von den modernen React-Konzepten abgeschnitten.
useEffect ist NICHT immer der richtige Hook.
Häufige Fehlanwendung: useEffect für abgeleitete Werte, für Event-Handler-ähnliche Logik, für Daten-Initialisierung aus Props. Faustregel: useEffect nur für Synchronisation mit der Außenwelt (DOM, Network, Subscriptions). Alles andere lässt sich oft direkter lösen.
Weiterführende Ressourcen
Externe Quellen
- Hooks – react.dev
- Rules of Hooks – react.dev
- Reusing Logic with Custom Hooks – react.dev
- eslint-plugin-react-hooks – npm