13 React Hooks – Übersicht und Zusammenhänge

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.

13.1 Die Hook-Kategorien

React’s Hooks lassen sich in sechs Hauptkategorien einteilen, die jeweils unterschiedliche Aspekte des Component-Lifecycles und der Anwendungsarchitektur adressieren.

13.1.1 1. State-Management: Daten verwalten

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' });

13.1.2 2. Side Effects & Lifecycle: Mit der Außenwelt interagieren

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;
}, []);

13.1.3 3. Referenzen & DOM: Persistenz ohne Reaktivität

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

13.1.4 4. Performance-Optimierung: Unnötige Arbeit vermeiden

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 });
}, []);

13.1.5 5. Context: Globale Daten teilen

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>;
}

13.1.6 6. Spezielle & Fortgeschrittene Hooks

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.

13.2 Die Hook-Landschaft: Visualisiert

Die Beziehungen zwischen Hooks lassen sich visuell darstellen. Verschiedene Diagramme beleuchten unterschiedliche Aspekte – Hierarchie, häufige Kombinationen, Entscheidungswege.

13.2.1 Hierarchische Übersicht: Die Hook-Familie

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.

13.2.2 Hook-Kombinationen: Patterns, die zusammen wirken

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.

13.2.3 Entscheidungsbaum: Welcher Hook wann?

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.

13.3 Häufige Hook-Kombinationen im Detail

13.3.1 Pattern 1: Async Data Loading mit Error Handling

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.

13.3.2 Pattern 2: Global State mit Context + Reducer

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.

13.3.3 Pattern 3: Performante Listen mit Memoization

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.

13.3.4 Pattern 4: DOM-Manipulation mit Refs und Layout-Effects

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.

13.4 Die Hook-Regeln: Warum sie existieren

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;
}

13.5 Custom Hooks: Wiederverwendbare Hook-Logik

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.

13.6 Die Hook-Landschaft verstehen

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.