React Hooks sind Funktionen wie useState, useEffect und useRef, die Funktionskomponenten State, Seiteneffekte und Refs geben. Dieses Cheat Sheet behandelt jeden eingebauten Hook in React 19.3 mit einem einzeiligen Zweck, einem Live-Beispiel und seinem häufigsten Fehler, dazu 12 Custom Hooks zum Kopieren.
Zum Zeitpunkt des Schreibens (Oktober 2026) ist das neueste React-Release 19.3. Die Tabelle listet jeden Hook aus der offiziellen React-Hooks-Referenz plus die drei React-19-Hooks, die auf eigenen Seiten dokumentiert sind (use, useOptimistic und der React-DOM-Hook useFormStatus). Mit React 18 markierte Hooks funktionieren in React 18 und 19. Mit React 19 markierte Hooks gibt es in React 18 nicht. Jeder React-18-Hook unten hat ein Live-Beispiel zum Anklicken; die React-19-Hooks werden nur als Code gezeigt, weil diese Seite auf React 18 läuft.
| Hook | Gruppe | Wofür | Seit |
|---|---|---|---|
| useState | State | einen Wert halten, der die Komponente bei Änderung neu rendert | React 18 |
| useReducer | State | State mit vielen zusammenhängenden Updates über eine Reducer-Funktion verwalten | React 18 |
| useContext | Context | einen Wert vom nächsten Provider darüber lesen | React 18 |
| useRef | Ref | einen veränderbaren Wert oder DOM-Knoten halten, der keine Renders auslöst | React 18 |
| useImperativeHandle | Ref | festlegen, welche Methoden ein Parent über eine Ref aufrufen kann | React 18 |
| useEffect | Effect | nach dem Zeichnen mit etwas außerhalb von React synchronisieren | React 18 |
| useLayoutEffect | Effect | Layout messen, bevor der Browser zeichnet | React 18 |
| useInsertionEffect | Effect | Style-Regeln einfügen, bevor Layout-Effects laufen (CSS-in-JS-Bibliotheken) | React 18 |
| useEffectEvent | Effect | die neuesten Props in einem Effect lesen, ohne ihn neu auszuführen | React 19.2 |
| useMemo | Performance | eine teure Berechnung zwischen Renders cachen | React 18 |
| useCallback | Performance | eine Funktion cachen, damit memoisierte Kinder nicht neu rendern | React 18 |
| useTransition | Performance | ein State-Update als nicht dringend markieren und ein Pending-Flag erhalten | React 18 |
| useDeferredValue | Performance | einen langsamen Teil der UI beim schnellen Tippen nachhinken lassen | React 18 |
| useId | Sonstige | stabile, eindeutige IDs für Labels und ARIA-Attribute erzeugen | React 18 |
| useSyncExternalStore | Sonstige | einen Store außerhalb von React abonnieren | React 18 |
| useDebugValue | Sonstige | einen Custom Hook in den React DevTools beschriften | React 18 |
| useActionState | Sonstige | Ergebnis und Pending-Status einer Formular-Action verfolgen | React 19 |
| useOptimistic | Sonstige | das erwartete Ergebnis einer Action zeigen, bevor sie fertig ist | React 19 |
| use | API | ein Promise oder einen Context lesen, auch in Bedingungen | React 19 |
| useFormStatus | React DOM | den Pending-Status des umgebenden Formulars lesen | React 19 |
Für Hooks gelten zwei Regeln: Rufe sie nur auf der obersten Ebene einer Komponente oder eines Custom Hooks auf (nie in Schleifen, Bedingungen oder verschachtelten Funktionen), und rufe sie nur aus React-Funktionskomponenten oder anderen Hooks auf. use ist die einzige Ausnahme von der ersten Regel: Es darf in if-Blöcken und Schleifen aufgerufen werden. Das Paket eslint-plugin-react-hooks prüft beide Regeln für dich.
useStateWas er macht: Speichert einen Wert für diese Komponente. Ein Aufruf des Setters plant ein Re-Render mit dem neuen Wert.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount((c) => c - 1)}>-1</button>
<span>{count}</span>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
</>
);
}
Häufiger Fehler: setCount(count + 1) dreimal in einem Klick schreiben und +3 erwarten. Alle drei lesen dasselbe count aus diesem Render, also bekommst du +1. Übergib eine Updater-Funktion, setCount(c => c + 1), wenn der nächste Wert vom vorherigen abhängt. Der Button "+3 (Updater)" oben macht genau das. Ein zweiter Klassiker: ein Objekt oder Array direkt verändern (user.name = "x"; setUser(user)). React vergleicht per Referenz, sieht dasselbe Objekt und überspringt den Render. Erzeuge stattdessen ein neues Objekt: setUser({ ...user, name: "x" }).
useReducerWas er macht: Verlagert die Update-Logik in eine reine Funktion, die den aktuellen State und eine Action entgegennimmt und den nächsten State zurückgibt. Nützlich, wenn sich mehrere Werte gemeinsam ändern.
type Action = { type: "add"; text: string } | { type: "toggle"; id: number };
function reducer(state: Todo[], action: Action): Todo[] {
switch (action.type) {
case "add":
return [...state, { id: Date.now(), text: action.text, done: false }];
case "toggle":
return state.map((t) => (t.id === action.id ? { ...t, done: !t.done } : t));
}
}
const [todos, dispatch] = useReducer(reducer, []);
dispatch({ type: "add", text: "Ship it" });
Häufiger Fehler: Seiteneffekte im Reducer ausführen (Daten laden, in localStorage schreiben, zufällige IDs aus einem Timer erzeugen, auf die du dich später verlässt). Im Strict Mode der Entwicklung ruft React Reducer zweimal auf, um unreinen Code aufzudecken. Halte den Reducer rein und lege Seiteneffekte in Event-Handler oder Effects.
useContextWas er macht: Liest den Wert des nächstgelegenen passenden Providers oberhalb dieser Komponente und rendert neu, wenn sich dieser Wert ändert.
const ThemeContext = createContext<"light" | "dark">("light");
function App() {
const [theme, setTheme] = useState<"light" | "dark">("light");
return (
<ThemeContext.Provider value={theme}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Badge() {
const theme = useContext(ThemeContext);
return <span className={theme}>Current theme: {theme}</span>;
}
Häufiger Fehler: Bei jedem Render ein neues Objektliteral als Provider-Wert übergeben, etwa value={{ user, setUser }}. Jeder Render erzeugt ein neues Objekt, also rendert jeder Consumer neu, auch wenn sich nichts geändert hat. Packe den Wert in useMemo oder teile selten und oft wechselnde Werte auf zwei Contexts auf. Prüfe auch, dass der Provider oberhalb der Komponente sitzt: Ein Aufruf von useContext in derselben Komponente, die den Provider rendert, liefert den Standardwert.
useRefWas er macht: Gibt dir eine Box (ref.current), die Re-Renders überlebt. Eine Änderung löst keinen Render aus. Nutze sie für DOM-Knoten, Timer-IDs und andere Werte, die der Bildschirm nicht anzeigt.
const inputRef = useRef<HTMLInputElement>(null);
const clicks = useRef(0);
<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>Focus</button>
<button onClick={() => { clicks.current += 1; }}>Count silently</button>
Häufiger Fehler: ref.current im JSX anzeigen und erwarten, dass es sich aktualisiert. Refs lösen keine Renders aus, der Bildschirm zieht also erst nach, wenn etwas anderes neu rendert (probiere oben den Button "Zur Ref addieren" und dann "Neu rendern"). Soll der Wert auf dem Bildschirm erscheinen, gehört er in den State. Vermeide außerdem, Refs während des Renderns zu lesen oder zu schreiben, außer bei einer einmaligen Lazy-Initialisierung.
useImperativeHandleWas er macht: Legt fest, was ein Parent erhält, wenn er eine Ref an deine Komponente hängt, sodass du einige benannte Methoden statt des ganzen DOM-Knotens freigibst.
type Handle = { shake: () => void; clear: () => void };
// React 18: wrap in forwardRef. In React 19 you can accept ref as a normal prop.
const FancyInput = forwardRef<Handle, Props>(function FancyInput(props, ref) {
const el = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
shake() { /* animate */ el.current?.focus(); },
clear() { if (el.current) el.current.value = ""; },
}), []);
return <input ref={el} />;
});
// parent
const ref = useRef<Handle>(null);
<FancyInput ref={ref} />
<button onClick={() => ref.current?.shake()}>Shake</button>
Häufiger Fehler: Ihn für Dinge nutzen, die Props ausdrücken können. Wenn ein Parent ref.current.open() aufruft, um ein Modal zu zeigen, ist eine isOpen-Prop meist einfacher und leichter nachzuvollziehen. Behalte imperative Handles für Fokus, Scrollen, Play/Pause und Animationen. Wer das Dependency-Array (das dritte Argument) vergisst, erzeugt das Handle außerdem bei jedem Render neu.
useEffectWas er macht: Führt Code aus, nachdem React den Bildschirm aktualisiert hat, um eine Komponente mit etwas außerhalb von React synchron zu halten: einem Timer, einem Abo, einer Netzwerkanfrage, einem Widget ohne React. Gib eine Cleanup-Funktion zurück, um es rückgängig zu machen.
useEffect(() => {
if (!running) return;
const id = setInterval(() => setSeconds((s) => s + 1), 1000);
return () => clearInterval(id); // runs before the next effect and on unmount
}, [running]);
Häufiger Fehler: Einen Effect nutzen, um etwas zu berechnen, das du während des Renderns berechnen könntest, etwa useEffect(() => setFullName(first + " " + last), [first, last]). Das kostet einen zusätzlichen Render und kann für einen Frame veraltete Werte zeigen. Schreibe stattdessen const fullName = first + " " + last. Der zweithäufigste Bug ist ein fehlendes Cleanup: Im Strict Mode der Entwicklung mountet, unmountet und mountet React jede Komponente einmal neu, ein Effect ohne Cleanup abonniert also doppelt oder startet einen Timer zweimal. Dieser doppelte Lauf ist ein Hinweis, kein Bug in React.
useLayoutEffectWas er macht: Gleiche Form wie useEffect, läuft aber, nachdem React das DOM geändert hat und bevor der Browser zeichnet. Nutze ihn, um ein Element zu messen und das Layout anzupassen, damit Nutzer nie die falsche Position sehen.
const boxRef = useRef<HTMLSpanElement>(null);
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
setWidth(boxRef.current!.getBoundingClientRect().width);
}, [text]);
Häufiger Fehler: Standardmäßig zu ihm greifen. Er blockiert das Zeichnen, langsame Arbeit darin lässt die Seite also hängen. Nutze useEffect, außer du siehst ein Flackern, das nur eine Messung vor dem Zeichnen behebt (Tooltips, Popover-Positionierung, automatische Größe). Beim Server-Rendering tut er außerdem nichts, was in manchen Setups eine Warnung erzeugt.
useInsertionEffectWas er macht: Läuft vor allen Layout-Effects, damit eine Bibliothek <style>-Regeln einfügen kann, bevor irgendetwas das Layout liest. Er ist für Autoren von CSS-in-JS-Bibliotheken gedacht.
function useScopedRule(selector: string, rule: string) {
useInsertionEffect(() => {
const style = document.createElement("style");
style.textContent = `${selector} { ${rule} }`;
document.head.appendChild(style);
return () => style.remove();
}, [selector, rule]);
}
Häufiger Fehler: Ihn in Anwendungscode nutzen oder darin State aktualisieren wollen. Du kannst darin keine Updates planen, und Refs sind noch nicht angehängt. Wenn du keine Styling-Bibliothek schreibst, willst du fast sicher useEffect oder useLayoutEffect.
useEffectEventWas er macht: Ab React 19.2. Umschließt eine Funktion, sodass ein Effect sie aufrufen kann und immer die neuesten Props und den neuesten State sieht, ohne diese Werte als Abhängigkeiten aufzulisten. Nutze ihn für den "Event"-Teil eines Effects, etwa Logging oder eine Benachrichtigung mit dem aktuellen Theme.
// React 19.2+ only. Not run on this page.
import { useEffect, useEffectEvent } from "react";
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification("Connected", theme); // always the latest theme
});
useEffect(() => {
const conn = createConnection(roomId);
conn.on("connected", () => onConnected());
conn.connect();
return () => conn.disconnect();
}, [roomId]); // theme is not a dependency, and onConnected must not be listed
}
Häufiger Fehler: Das Effect-Event ins Dependency-Array schreiben, es während des Renderns aufrufen oder an andere Komponenten weitergeben. Laut React-Dokumentation sollen Effect-Events nur aus Effects (oder anderen Effect-Events) aufgerufen werden, und die Lint-Regel warnt, wenn du eines als Abhängigkeit auflistest. Unter React 18 ist der übliche Ersatz das "Latest Ref"-Muster, das useInterval weiter unten auf dieser Seite nutzt.
useMemoWas er macht: Cacht das Ergebnis einer Berechnung und rechnet nur neu, wenn sich die Abhängigkeiten ändern.
const filtered = useMemo(
() => items.filter((item) => item.includes(query)),
[items, query],
);
Häufiger Fehler: Alles darin einpacken. Billige Arbeit wie a + b oder ein kurzes map zu memoisieren, bringt Code und Vergleichskosten ohne Nutzen. Miss zuerst (React DevTools Profiler) und memoisiere langsame Berechnungen, Werte für memo-Kinder und Werte, die als Effect-Abhängigkeiten dienen. Eine verwandte Falle: Eine Abhängigkeit, die bei jedem Render ein neues Objekt ist ({ sort: "asc" }), lässt das Memo jedes Mal neu rechnen.
useCallbackWas er macht: Gibt zwischen Renders dieselbe Funktionsinstanz zurück, bis sich eine Abhängigkeit ändert. Das zählt, wenn die Funktion an ein in memo verpacktes Kind übergeben oder als Effect-Abhängigkeit genutzt wird.
const handleAdd = useCallback(() => setCount((c) => c + 1), []);
const Child = memo(function Child({ onClick }: { onClick: () => void }) {
return <button onClick={onClick}>Add</button>;
});
<Child onClick={handleAdd} />
Häufiger Fehler: useCallback nutzen, obwohl das Kind nicht memoisiert ist. Ohne memo am Kind rendert es ohnehin mit seinem Parent neu, die stabile Funktion ändert also nichts. Die Live-Demo zeigt beide Kinder: Nur das Kind mit der gecachten Funktion überspringt Renders. Veraltete Closures sind die andere Falle: Liest der Callback count, die Abhängigkeitsliste ist aber leer, sieht er weiter den ersten Wert. Nutze eine Updater-Funktion oder liste die Abhängigkeit auf.
useTransitionWas er macht: Markiert ein State-Update als nicht dringend. React hält den aktuellen Bildschirm reaktionsfähig, während es den neuen vorbereitet, und gibt dir ein isPending-Flag für einen Ladehinweis.
const [isPending, startTransition] = useTransition();
function selectTab(next: string) {
startTransition(() => {
setTab(next);
});
}
Häufiger Fehler: Den eigenen State eines Texteingabefelds in eine Transition packen. Kontrollierte Inputs müssen dringend aktualisiert werden, sonst fühlt sich das Tippen kaputt an. Lass den Input-State normal und verlege das langsame, abgeleitete Update in die Transition (oder nutze useDeferredValue). In React 18 muss die an startTransition übergebene Funktion synchron sein; React 19 akzeptiert auch async-Funktionen.
useDeferredValueWas er macht: Gibt dir eine Kopie eines Werts, die hinterherhinken darf. Schnelle Teile der UI nutzen den echten Wert; ein langsamer Teil rendert mit dem verzögerten und holt auf, wenn der Browser Leerlauf hat.
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<div style={{ opacity: isStale ? 0.5 : 1 }}>
<SlowResults query={deferredQuery} />
</div>
Häufiger Fehler: Vergessen, das langsame Kind in memo zu packen. Rendert das Kind ohnehin bei jedem Tastendruck mit dem Parent neu, hilft das Verzögern seiner Prop nicht. Es ist auch kein Debounce: Es wartet keine feste Zeit und reduziert keine Netzwerkanfragen. Für weniger Anfragen nutze Debouncing (siehe useDebounce unten).
useIdWas er macht: Erzeugt eine ID, die innerhalb der App eindeutig und auf Server und Client identisch ist, für htmlFor, aria-describedby und ähnliche Attribute.
function Field({ label }: { label: string }) {
const id = useId();
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} />
</>
);
}
Häufiger Fehler: Ihn für Listen-Keys nutzen. Keys sollten aus deinen Daten kommen (einer ID aus der Datenbank), nicht aus einem Hook. Vermeide außerdem Math.random() für Element-IDs in serverseitig gerenderten Apps: Server und Browser erzeugen unterschiedliche Zahlen und die Hydration schlägt fehl, genau das Problem, das useId löst.
useSyncExternalStoreWas er macht: Abonniert für eine Komponente eine Datenquelle, die nicht React gehört (eine Browser-API, einen globalen Store), und liest einen Snapshot, der beim Concurrent Rendering konsistent bleibt.
function subscribe(callback: () => void) {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
}
const online = useSyncExternalStore(
subscribe,
() => navigator.onLine, // client snapshot
() => true, // server snapshot
);
Häufiger Fehler: subscribe inline in der Komponente definieren. Eine neue Funktion bei jedem Render lässt React jedes Mal ab- und neu abonnieren. Definiere sie außerhalb der Komponente oder packe sie in useCallback. Die Snapshot-Funktion muss außerdem denselben Wert zurückgeben, wenn sich nichts geändert hat: Gibt sie bei jedem Aufruf ein neues Objekt oder Array zurück, entsteht eine endlose Render-Schleife.
useDebugValueWas er macht: Fügt einem Custom Hook in den React DevTools ein Label hinzu. Auf das, was Nutzer sehen, hat er keine Wirkung.
function useOnlineStatus() {
const online = useSyncExternalStore(subscribe, () => navigator.onLine);
useDebugValue(online ? "Online" : "Offline");
return online;
}
Häufiger Fehler: Ihn in jeden Custom Hook packen oder darin teure Formatierung ausführen. Nutze ihn für gemeinsam genutzte Hooks, deren State in den DevTools schwer lesbar ist, und übergib einen Formatter als zweites Argument, wenn das Label aufwendig zu bauen ist: useDebugValue(date, d => d.toISOString()).
use, useActionState, useOptimistic, useFormStatus) werden von React 18 nicht exportiert. Diese Seite selbst läuft auf React 18, deshalb werden sie als Code gezeigt, den du in ein React-19-Projekt kopieren kannst, ohne Live-Demos. useEffectEvent, oben behandelt, braucht React 19.2.useActionStateWas er macht: Umschließt eine Formular-Action und gibt dir ihr neuestes Ergebnis plus ein Pending-Flag. Die Action erhält den vorherigen State und die abgeschickten FormData.
// React 19+
import { useActionState } from "react";
async function subscribe(prev: { error?: string; ok?: boolean }, formData: FormData) {
const email = String(formData.get("email") ?? "");
if (!email.includes("@")) return { error: "Enter a valid email" };
await saveEmail(email);
return { ok: true };
}
function Newsletter() {
const [state, formAction, isPending] = useActionState(subscribe, {});
return (
<form action={formAction}>
<input name="email" type="email" />
<button disabled={isPending}>{isPending ? "Saving..." : "Subscribe"}</button>
{state.error && <p role="alert">{state.error}</p>}
{state.ok && <p>Thanks, you are subscribed.</p>}
</form>
);
}
Häufiger Fehler: Vergessen, dass das erste Argument der Action der vorherige State ist. Schreibst du async function subscribe(formData), ist formData in Wahrheit der alte State, und .get wirft einen Fehler. Beachte auch, dass er während der Canary-Phase von React 19 kurz useFormState (in react-dom) hieß; die aktuelle Doku nutzt useActionState aus react.
useOptimisticWas er macht: Zeigt eine vorläufige, optimistische Version des States, während eine async-Action läuft, und kehrt nach ihrem Ende zum echten State zurück.
// React 19+
import { useOptimistic } from "react";
function Thread({ messages, send }: Props) {
const [optimistic, addOptimistic] = useOptimistic(
messages,
(current, text: string) => [...current, { text, sending: true }],
);
async function action(formData: FormData) {
const text = String(formData.get("text"));
addOptimistic(text);
await send(text); // parent updates `messages` when this resolves
}
return (
<form action={action}>
{optimistic.map((m, i) => <p key={i}>{m.text}{m.sending && " (sending)"}</p>)}
<input name="text" />
</form>
);
}
Häufiger Fehler: Den optimistischen Setter außerhalb einer Action oder Transition aufrufen. React erwartet ihn in einer Formular-Action oder in startTransition; anderswo erscheint der optimistische Wert und verschwindet sofort wieder (React warnt in der Entwicklung). Und behandle immer Fehler: Wirft send einen Fehler, zeige eine Meldung, damit die Nachricht nicht stillschweigend verschwindet.
useWas er macht: Liest den Wert eines Promise (und suspendiert, bis es aufgelöst ist) oder eines Context. Anders als andere Hooks kann er in Bedingungen und Schleifen aufgerufen werden.
// React 19+
import { use, Suspense } from "react";
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise); // suspends until resolved
return comments.map((c) => <p key={c.id}>{c.text}</p>);
}
function Page({ commentsPromise }) {
return (
<Suspense fallback={<p>Loading comments...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}
// Conditional context read, which useContext cannot do:
function Heading({ show }: { show: boolean }) {
if (!show) return null;
const theme = use(ThemeContext);
return <h1 className={theme}>Hello</h1>;
}
Häufiger Fehler: Das Promise in der Komponente erzeugen, die use aufruft, etwa use(fetch(url)). Jeder Render erzeugt ein neues Promise, die Komponente suspendiert also für immer. Erzeuge das Promise einmal (in einer Server-Komponente, einem Loader, einem Cache oder einem Parent) und reiche es nach unten. use darf außerdem nicht in try/catch stehen; behandle Fehler mit einer Error Boundary.
useFormStatusWas er macht: Wird aus react-dom importiert. Liest den Status des umgebenden <form>: pending, data, method und action.
// React 19+
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>{pending ? "Sending..." : "Send"}</button>;
}
function ContactForm() {
return (
<form action={sendMessage}>
<textarea name="message" />
<SubmitButton />
</form>
);
}
Häufiger Fehler: Ihn in derselben Komponente aufrufen, die das <form> rendert. Er meldet nur den Status eines Formulars oberhalb der aufrufenden Komponente, in der eigenen Komponente des Formulars ist pending also immer false. Verschiebe den Button in eine eigene Komponente, wie oben.
useEffect, useLayoutEffect, useMemo, useCallback and useImperativeHandle nehmen als letztes Argument eine Liste von Werten. React vergleicht jeden Eintrag mit Object.is mit dem Wert aus dem vorherigen Render. Hat sich ein Eintrag geändert, läuft der Effect erneut (oder das Memo rechnet neu). Diese eine Regel erklärt jeden Fall:
| Du schreibst | Der Effect läuft | Typischer Einsatz |
|---|---|---|
| useEffect(fn) | nach jedem Render | selten; meist ein Zeichen, dass etwas während des Renderns berechnet werden sollte |
| useEffect(fn, []) | einmal nach dem Mount (im Strict Mode der Entwicklung zweimal), Cleanup beim Unmount | Abos und Setup, die keine Props oder State nutzen |
| useEffect(fn, [a, b]) | nach dem Mount, dann immer wenn sich a oder b ändert | Laden per ID, Synchronisieren mit einer Prop |
Klicke die Buttons: Eine Änderung von b führt nur den Effect ohne Array erneut aus, eine Änderung von a führt diesen und den [a]-Effect erneut aus, und der []-Effect bleibt nach dem Mount still.
eslint-plugin-react-hooks und lass dir von exhaustive-deps sagen, was fehlt.{ id } oder () => {} ist unter Object.is nie gleich dem vorherigen, sie lösen den Effect also bei jedem Render erneut aus. Verschiebe sie in den Effect, aus der Komponente heraus oder memoisiere sie.[user.id] ist stabiler als [user], wenn nur die ID zählt.setCount(c => c + 1) liest count nicht, also muss count nicht aufgelistet werden.useEffectEvent ab React 19.2 oder unter React 18 eine Ref, die du aktuell hältst.// Stale closure: logs 0 forever
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, []); // count is missing
// Fixed: re-subscribe when count changes
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, [count]);
// Fetch with cleanup so a slow old response cannot overwrite a newer one
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then((r) => r.json())
.then(setUser)
.catch((err) => { if (err.name !== "AbortError") setError(err); });
return () => controller.abort();
}, [userId]);
Ein Custom Hook ist eine Funktion, deren Name mit use beginnt und die andere Hooks aufruft. So können mehrere Komponenten zustandsbehaftete Logik teilen; jede aufrufende Komponente bekommt ihren eigenen, unabhängigen State. Die Hooks unten wurden für diese Seite in TypeScript geschrieben, funktionieren unter React 18 und 19 und haben keine Abhängigkeiten. Jeder endet mit einem Hinweis, wann du ihn nicht nutzen solltest.
useDebounceGibt einen Wert zurück, der sich erst aktualisiert, wenn sich die Eingabe delay Millisekunden lang nicht mehr geändert hat. Gut für Suchfelder, die eine API aufrufen.
import { useEffect, useState } from "react";
export function useDebounce<T>(value: T, delay = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = window.setTimeout(() => setDebounced(value), delay);
return () => window.clearTimeout(id);
}, [value, delay]);
return debounced;
}
// const debouncedQuery = useDebounce(query, 400);
// useEffect(() => { search(debouncedQuery); }, [debouncedQuery]);
Wann du ihn nicht nutzen solltest: Wenn die UI nur beim Rendern einer großen Liste reaktionsfähig bleiben soll. Das ist ein Rendering-Problem, und useDeferredValue löst es ohne künstliche Verzögerung. Verzichte auch bei Formularvalidierung darauf, die sich sofort anfühlen soll.
useLocalStorageFunktioniert wie useState, speichert den Wert aber in localStorage und stellt ihn beim nächsten Besuch wieder her.
import { useEffect, useState } from "react";
export function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => {
try {
const raw = window.localStorage.getItem(key);
return raw === null ? initial : (JSON.parse(raw) as T);
} catch {
return initial; // private mode, blocked storage or bad JSON
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch {
/* storage full or blocked: keep the in-memory value */
}
}, [key, value]);
return [value, setValue] as const;
}
Wann du ihn nicht nutzen solltest: Für alles Sensible (Tokens, persönliche Daten), denn jedes Script auf der Seite kann localStorage lesen. Auch nicht ohne Vorsicht für serverseitig gerenderte Seiten: Der Server hat kein window, lies den Speicher also nach dem Mount, um einen Hydration-Mismatch zu vermeiden. Er synchronisiert nicht zwischen Tabs; füge einen storage-Event-Listener hinzu, wenn du das brauchst.
useMediaQueryGibt true zurück, solange eine CSS-Media-Query zutrifft, und aktualisiert sich, wenn sich das ändert.
import { useCallback, useSyncExternalStore } from "react";
export function useMediaQuery(query: string): boolean {
const subscribe = useCallback((onChange: () => void) => {
const mql = window.matchMedia(query);
mql.addEventListener("change", onChange);
return () => mql.removeEventListener("change", onChange);
}, [query]);
return useSyncExternalStore(
subscribe,
() => window.matchMedia(query).matches,
() => false, // server snapshot
);
}
// const isDesktop = useMediaQuery("(min-width: 1024px)");
// const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
Wann du ihn nicht nutzen solltest: Für Layout-Änderungen, die CSS selbst erledigen kann. Elemente mit Tailwind-Breakpoints auszublenden oder umzuordnen (md:hidden) funktioniert, bevor JavaScript lädt, und flackert nie. Nutze den Hook, wenn du andere Komponenten rendern oder etwas gar nicht erst laden musst.
useToggleEin Boolean mit einer stabilen toggle-Funktion, für Menüs, Modals und aufklappbare Abschnitte.
import { useCallback, useState } from "react";
export function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn((v) => !v), []);
return [on, toggle, setOn] as const;
}
// const [open, toggleOpen, setOpen] = useToggle();
Wann du ihn nicht nutzen solltest: Wenn der State mehr als zwei Werte hat (geschlossen, öffnet, offen, Fehler). Nutze eine String-Union mit useState oder useReducer, statt mehrere Toggles zu stapeln.
usePreviousGibt den Wert aus dem vorherigen Render zurück, praktisch, um eine Änderung zu zeigen ("gestiegen von 12") oder auf einen Übergang zu reagieren.
import { useEffect, useRef } from "react";
export function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Wann du ihn nicht nutzen solltest: Um zu entscheiden, ob State aktualisiert werden soll. Die React-Dokumentation zeigt dafür ein saubereres Muster: Speichere den vorherigen Wert im State und vergleiche während des Renderns. Dieser Hook liest eine Ref während des Renderns, was heute funktioniert, aber zu der Art Code gehört, die du laut React-Leitfaden selten halten sollst.
useOnClickOutsideRuft einen Handler auf, wenn der Nutzer irgendwo außerhalb eines bestimmten Elements drückt. Typisch zum Schließen eines eigenen Menüs oder Popovers.
import { useEffect, useRef, type RefObject } from "react";
export function useOnClickOutside(
ref: RefObject<HTMLElement>,
handler: (event: PointerEvent) => void,
) {
const saved = useRef(handler);
useEffect(() => { saved.current = handler; }, [handler]);
useEffect(() => {
const listener = (event: PointerEvent) => {
if (!ref.current || ref.current.contains(event.target as Node)) return;
saved.current(event);
};
document.addEventListener("pointerdown", listener);
return () => document.removeEventListener("pointerdown", listener);
}, [ref]);
}
Wann du ihn nicht nutzen solltest: Wenn du für Menü oder Dialog schon eine Headless-Bibliothek (Radix, Headless UI) nutzt: Sie übernehmen Klicks außerhalb, Escape und die Fokus-Rückgabe für dich. Einem selbstgebauten Menü, das nur auf diesem Hook basiert, fehlt meist die Tastaturunterstützung.
useIntervalEin setInterval, das immer den neuesten Callback aufruft und sich pausieren lässt, indem du null als Verzögerung übergibst.
import { useEffect, useRef } from "react";
export function useInterval(callback: () => void, delay: number | null) {
const saved = useRef(callback);
useEffect(() => { saved.current = callback; }, [callback]);
useEffect(() => {
if (delay === null) return;
const id = window.setInterval(() => saved.current(), delay);
return () => window.clearInterval(id);
}, [delay]);
}
// useInterval(() => setTicks((t) => t + 1), running ? 1000 : null);
Wann du ihn nicht nutzen solltest: Für Animationen (nutze CSS oder requestAnimationFrame) oder für genaue Uhren: Intervalle driften, und Browser bremsen sie in Hintergrund-Tabs. Berechne für einen Countdown die Restzeit bei jedem Tick aus einem festen End-Zeitstempel.
useWindowSizeGibt die aktuelle Fensterbreite und -höhe zurück und aktualisiert sich bei Größenänderung.
import { useSyncExternalStore } from "react";
function subscribe(onChange: () => void) {
window.addEventListener("resize", onChange);
return () => window.removeEventListener("resize", onChange);
}
export function useWindowSize() {
const width = useSyncExternalStore(subscribe, () => window.innerWidth, () => 0);
const height = useSyncExternalStore(subscribe, () => window.innerHeight, () => 0);
return { width, height };
}
Wann du ihn nicht nutzen solltest: Für Breakpoints: useMediaQuery rendert nur neu, wenn die Query umschlägt, dieser hier dagegen bei jedem Pixel der Größenänderung. Für die Größe eines einzelnen Elements statt des Fensters nutze einen ResizeObserver. (Die zwei getrennten Snapshots sind Absicht: Ein neues Objekt aus einer Snapshot-Funktion würde endlos schleifen.)
useCopyToClipboardKopiert Text mit der Clipboard API und stellt ein kurzlebiges copied-Flag für Button-Feedback bereit.
import { useCallback, useState } from "react";
export function useCopyToClipboard(resetAfter = 1500) {
const [copied, setCopied] = useState(false);
const copy = useCallback(async (text: string) => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
window.setTimeout(() => setCopied(false), resetAfter);
return true;
} catch {
setCopied(false); // insecure context or permission denied
return false;
}
}, [resetAfter]);
return [copied, copy] as const;
}
Wann du ihn nicht nutzen solltest: Auf Seiten, die über einfaches HTTP ausgeliefert werden: navigator.clipboard braucht einen sicheren Kontext (HTTPS oder localhost). Zeige immer eine Alternative, etwa das Markieren des Texts, wenn er false zurückgibt.
useFetchLädt JSON für eine URL, verfolgt Lade- und Fehlerstatus und bricht die Anfrage ab, wenn sich die URL ändert oder die Komponente unmountet.
import { useEffect, useState } from "react";
type FetchState<T> = { data: T | null; error: Error | null; loading: boolean };
export function useFetch<T>(url: string | null): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({ data: null, error: null, loading: !!url });
useEffect(() => {
if (!url) return;
const controller = new AbortController();
setState((s) => ({ ...s, loading: true, error: null }));
fetch(url, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`Request failed with status ${res.status}`);
return res.json() as Promise<T>;
})
.then((data) => setState({ data, error: null, loading: false }))
.catch((error: Error) => {
if (error.name === "AbortError") return; // superseded or unmounted
setState({ data: null, error, loading: false });
});
return () => controller.abort();
}, [url]);
return state;
}
Wann du ihn nicht nutzen solltest: In größeren Apps. Er hat keinen Cache, keine Deduplizierung, keine Wiederholungen und keine Revalidierung, zwei Komponenten, die dieselbe URL abfragen, stellen also zwei Anfragen. Nutze das Datenladen deines Frameworks oder eine Datenbibliothek (TanStack Query, SWR), sobald du etwas davon brauchst. Die React-Doku selbst empfiehlt dasselbe für Datenabrufe in Effects.
useEventListenerHängt einen Event-Listener an das Fenster (oder ein beliebiges Element) und ruft immer den neuesten Handler auf, ohne bei jedem Render neu zu abonnieren.
import { useEffect, useRef } from "react";
export function useEventListener<K extends keyof WindowEventMap>(
type: K,
handler: (event: WindowEventMap[K]) => void,
target: Window | HTMLElement | null = typeof window === "undefined" ? null : window,
) {
const saved = useRef(handler);
useEffect(() => { saved.current = handler; }, [handler]);
useEffect(() => {
if (!target) return;
const listener = (event: Event) => saved.current(event as WindowEventMap[K]);
target.addEventListener(type, listener);
return () => target.removeEventListener(type, listener);
}, [type, target]);
}
// useEventListener("keydown", (e) => { if (e.key === "Escape") close(); });
Wann du ihn nicht nutzen solltest: Für Events, die React an von dir gerenderten Elementen bereits behandelt: Nutze onClick, onKeyDown und Co. direkt im JSX. Greif zu diesem Hook nur für window, document oder Elemente von Drittanbietern.
useIsMountedGibt eine Funktion zurück, die dir sagt, ob die Komponente noch auf dem Bildschirm ist, für älteren async-Code, der nach einem await State setzt.
import { useCallback, useEffect, useRef } from "react";
export function useIsMounted() {
const mounted = useRef(false);
useEffect(() => {
mounted.current = true;
return () => { mounted.current = false; };
}, []);
return useCallback(() => mounted.current, []);
}
// const isMounted = useIsMounted();
// const data = await load(); if (isMounted()) setData(data);
Wann du ihn nicht nutzen solltest: Als Standard. React 18 hat die Warnung zum Setzen von State in unmounteten Komponenten entfernt, und die bessere Lösung ist, die Arbeit selbst abzubrechen (ein AbortController, wie in useFetch) oder veraltete Ergebnisse mit einem Flag im Cleanup des Effects zu ignorieren. Behalte ihn für älteren Code, den du noch nicht umbauen kannst.
useState (oder useReducer, wenn sich mehrere Werte gemeinsam ändern).useRef.useContext oder ein externer Store, gelesen mit useSyncExternalStore.useEffect; wechsle zu useLayoutEffect nur für Messungen, die sichtbares Flackern verursachen.useMemo. Ein memoisiertes Kind rendert wegen einer Funktions-Prop ständig neu: useCallback.useDeferredValue oder useTransition.useActionState, useFormStatus, useOptimistic.Du baust eher eine Komponentenbibliothek als einzelne Hooks? Unser Vergleich von React-UI-Bibliotheken zeigt dieselben sechs Komponenten in shadcn/ui, Radix, Headless UI, daisyUI, Flowbite und MUI, und das Tailwind-Cheat-Sheet behandelt die Utility-Klassen aus den Demos auf dieser Seite. Fertige Bausteine mit React und Tailwind findest du in der Komponenten-Galerie.
Zum Zeitpunkt des Schreibens (Oktober 2026, React 19.3) listet die React-Hooks-Referenz 17 Hooks in React selbst: useState, useReducer, useContext, useRef, useImperativeHandle, useEffect, useLayoutEffect, useInsertionEffect, useEffectEvent, useMemo, useCallback, useTransition, useDeferredValue, useDebugValue, useId, useSyncExternalStore und useActionState. React 19 ergänzt außerdem useOptimistic und die use-API, und React DOM ergänzt useFormStatus.
useMemo cacht das Ergebnis eines Funktionsaufrufs, useCallback dagegen die Funktion selbst. useCallback(fn, deps) entspricht useMemo(() => fn, deps). Nutze useMemo für teure Werte und useCallback für Funktionen, die an memoisierte Kinder übergeben oder als Effect-Abhängigkeiten genutzt werden.
In der Entwicklung mit aktiviertem Strict Mode mountet React jede Komponente, unmountet sie und mountet sie erneut, damit du Effects ohne Cleanup findest. In einem Produktions-Build passiert das nicht. Verursacht der doppelte Lauf einen Bug, füge eine Cleanup-Funktion hinzu, die rückgängig macht, was der Effect gestartet hat.
Ein leeres Array sagt React, dass der Effect keine Props oder State liest, er läuft also einmal nach dem Mount der Komponente, und sein Cleanup läuft beim Unmount. Liest der Effect doch Props oder State, liste sie auf, sonst nutzt er weiter die Werte aus dem ersten Render.
Nein. use, useActionState, useOptimistic und useFormStatus werden von React 18 nicht exportiert, und useEffectEvent braucht React 19.2 oder neuer. Unter React 18 erreichst du Ähnliches mit useState plus Event-Handlern für Formulare und mit einer Ref, die den neuesten Callback hält, statt useEffectEvent.
Ihr Name beginnt mit use, und sie ruft mindestens einen anderen Hook auf. Durch diese Benennung können Reacts Lint-Regeln prüfen, ob sie die Hook-Regeln einhält. Jede Komponente, die einen Custom Hook aufruft, bekommt ihren eigenen State; Hooks teilen Logik, keine Daten.
Live: {title}
+3 (Updater)
Zurücksetzen
Den Abschnitt zu useReducer lesen
Eigenen Eintrag hinzufügen
Neuer Eintrag
Neue Aufgabe
Erledigte entfernen
Ich lese "{theme}" aus dem Context
Provider-Wert umschalten
Klicke den Button, um mich zu fokussieren
Fokusziel
Eingabe fokussieren
Zur Ref addieren (kein Re-Render)
Neu rendern
Ref-Wert beim letzten Render: {n}
Hier tippen
Kind-Eingabe
Parent ruft shake() auf
Parent ruft clear() auf
Effect lief: Intervall gestartet
Cleanup lief: Intervall gelöscht
Pausieren
Fortsetzen
Ändere meine Größe durch Tippen
Zu messender Text
Vor dem Zeichnen gemessen: {n}px breit
Rahmenfarbe
Diese Regel wurde in den head der Seite eingefügt
4,000 Einträge filtern
Einträge filtern
Unabhängiger Re-Render ({n})
{n} Treffer. Der Filter lief {runs}-mal (unabhängige Re-Renders lösen ihn nicht aus).
{label} (Kind {n}x gerendert)
Inline-Funktion
Parent neu rendern
Zähler: {n}
Über uns
Langsame Liste
Kontakt
Wechsle (alter Tab bleibt klickbar)...
Angezeigt: {tab}
Zeile
Inhalt des Tabs {tab}.
Ergebnisse gerendert für: "{query}"
Tippe schnell: Die Eingabe bleibt reaktionsfähig
Suche
Name
Netzwerk:
Fensterbreite:
Ändere die Fenstergröße oder schalte dein Netzwerk ab, um die Aktualisierung zu sehen.
Diese Komponente ruft einen Custom Hook auf, der sich in den React DevTools mit "{label}" beschriftet. Öffne den Tab Components, um das Label neben dem Hook zu sehen.
Online
Offline
Dependency-Arrays
[kein Array] lief (Render {n})
[] lief einmal beim Mount
[a] lief, a = {n}
a ändern ({n})
b ändern ({n})
Log leeren
Tippe und halte dann eine halbe Sekunde inne
Debounce-Eingabe
Sofort: "{text}" · Verzögert: "{debounced}"
Tippe und lade dann die Seite neu
Gespeicherter Name
Nur in diesem Browser gespeichert.
Nur in diesem Browser gespeichert: "{name}".
useMediaQuery: Viewport ist {size}, Systemthema ist {theme}.
768px oder breiter
schmaler als 768px
An
Aus
Zähler {n}
vorher war {value}
noch nichts
Stopp
Start
{n} Ticks
Kopiert
Einen Befehl kopieren
Menü öffnen
Klicke irgendwo außerhalb des Menüs, um es zu schließen.
Kostenlos starten · Keine Kreditkarte nötig