React Hooks Cheat Sheet: jeder Hook mit Live-Beispiel

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.

Alle eingebauten Hooks im Überblick

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.

Alle 20 werden angezeigt
HookGruppeWofürSeit
useStateStateeinen Wert halten, der die Komponente bei Änderung neu rendertReact 18
useReducerStateState mit vielen zusammenhängenden Updates über eine Reducer-Funktion verwaltenReact 18
useContextContexteinen Wert vom nächsten Provider darüber lesenReact 18
useRefRefeinen veränderbaren Wert oder DOM-Knoten halten, der keine Renders auslöstReact 18
useImperativeHandleReffestlegen, welche Methoden ein Parent über eine Ref aufrufen kannReact 18
useEffectEffectnach dem Zeichnen mit etwas außerhalb von React synchronisierenReact 18
useLayoutEffectEffectLayout messen, bevor der Browser zeichnetReact 18
useInsertionEffectEffectStyle-Regeln einfügen, bevor Layout-Effects laufen (CSS-in-JS-Bibliotheken)React 18
useEffectEventEffectdie neuesten Props in einem Effect lesen, ohne ihn neu auszuführenReact 19.2
useMemoPerformanceeine teure Berechnung zwischen Renders cachenReact 18
useCallbackPerformanceeine Funktion cachen, damit memoisierte Kinder nicht neu rendernReact 18
useTransitionPerformanceein State-Update als nicht dringend markieren und ein Pending-Flag erhaltenReact 18
useDeferredValuePerformanceeinen langsamen Teil der UI beim schnellen Tippen nachhinken lassenReact 18
useIdSonstigestabile, eindeutige IDs für Labels und ARIA-Attribute erzeugenReact 18
useSyncExternalStoreSonstigeeinen Store außerhalb von React abonnierenReact 18
useDebugValueSonstigeeinen Custom Hook in den React DevTools beschriftenReact 18
useActionStateSonstigeErgebnis und Pending-Status einer Formular-Action verfolgenReact 19
useOptimisticSonstigedas erwartete Ergebnis einer Action zeigen, bevor sie fertig istReact 19
useAPIein Promise oder einen Context lesen, auch in BedingungenReact 19
useFormStatusReact DOMden Pending-Status des umgebenden Formulars lesenReact 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.

State-Hooks

useState

Was 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" }).

useReducer

Was 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.

Context-Hooks

useContext

Was 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.

Ref-Hooks

useRef

Was 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.

useImperativeHandle

Was 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.

Effect-Hooks

useEffect

Was 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.

useLayoutEffect

Was 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.

useInsertionEffect

Was 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.

useEffectEvent

Was 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.

Performance-Hooks

useMemo

Was 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.

useCallback

Was 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.

useTransition

Was 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.

useDeferredValue

Was 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).

Weitere Hooks

useId

Was 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.

useSyncExternalStore

Was 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.

useDebugValue

Was 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()).

React-19-Hooks (nur Code, unter React 18 nicht lauffähig)

Nur React 19. Die vier Hooks in diesem Abschnitt (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.

useActionState

Was 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.

useOptimistic

Was 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.

use

Was 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.

useFormStatus

Was 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.

Das Dependency-Array erklärt

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 schreibstDer Effect läuftTypischer Einsatz
useEffect(fn)nach jedem Renderselten; 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 UnmountAbos und Setup, die keine Props oder State nutzen
useEffect(fn, [a, b])nach dem Mount, dann immer wenn sich a oder b ändertLaden 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.

Fünf Regeln, die die meisten Dependency-Bugs verhindern

  1. Liste jeden reaktiven Wert auf, den der Callback liest. Props, State und alles, was im Komponentenkörper deklariert ist. Lässt du einen weg, bekommst du eine veraltete Closure: Der Callback behält den Wert aus einem alten Render. Installiere eslint-plugin-react-hooks und lass dir von exhaustive-deps sagen, was fehlt.
  2. Während des Renderns erzeugte Objekte und Funktionen sind jedes Mal neu. { 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.
  3. Hänge, wo möglich, von primitiven Werten ab. [user.id] ist stabiler als [user], wenn nur die ID zählt.
  4. Nutze Updater-Funktionen, um State-Abhängigkeiten loszuwerden. setCount(c => c + 1) liest count nicht, also muss count nicht aufgelistet werden.
  5. Bring den Linter nicht zum Schweigen, um eine Schleife zu "reparieren". Eine Endlosschleife heißt meist, dass der Effect State setzt, der auch eine Abhängigkeit ist, oder von einem frischen Objekt abhängt. Behebe die Ursache. Für Werte, die du lesen willst, ohne auf sie zu reagieren, nutze 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]);

12 Custom Hooks zum Kopieren in dein Projekt

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.

1. useDebounce

Gibt 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.

2. useLocalStorage

Funktioniert 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.

3. useMediaQuery

Gibt 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.

4. useToggle

Ein 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.

5. usePrevious

Gibt 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.

6. useOnClickOutside

Ruft 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.

7. useInterval

Ein 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.

8. useWindowSize

Gibt 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.)

9. useCopyToClipboard

Kopiert 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.

10. useFetch

Lä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.

11. useEventListener

Hä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.

12. useIsMounted

Gibt 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.

Welchen Hook brauche ich? Eine schnelle Entscheidungsliste

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.

Häufige Fragen

Wie viele eingebaute Hooks hat React?

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.

Was ist der Unterschied zwischen useMemo und useCallback?

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.

Warum läuft mein useEffect zweimal?

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.

Was bedeutet ein leeres Dependency-Array in useEffect?

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.

Kann ich React-19-Hooks wie use oder useActionState in React 18 nutzen?

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.

Was macht eine Funktion zu einem Custom Hook?

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

E-Mail

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

Verwandt: Ratgeber

Produkt

Für wen

Funktionen

Ressourcen

Unternehmen

Sitemap