DevToolBoxFREE
BlogAdvertise

React Hooks Komplett-Guide

15 minvon DevToolBox

React Hooks haben die Art, React-Komponenten zu schreiben, revolutioniert. Seit React 16.8 ermoglichen Hooks State, Lifecycle und mehr in funktionalen Komponenten. Dieser vollstandige React Hooks Guide behandelt jeden Hook mit praktischen Beispielen.

useState: Komponentenstate verwalten

useState ist der grundlegendste Hook.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(prev => prev - 1)}>Decrement</button>
    </div>
  );
}

// Lazy initialization — runs only on first render
const [data, setData] = useState(() => {
  return JSON.parse(localStorage.getItem('data') || '{}');
});

// Updating objects — always create a new object
const [user, setUser] = useState({ name: '', age: 0 });
setUser(prev => ({ ...prev, name: 'Alice' }));

// Updating arrays — use spread or filter/map
const [items, setItems] = useState<string[]>([]);
setItems(prev => [...prev, 'new item']);
setItems(prev => prev.filter(item => item !== 'remove me'));

useEffect: Seiteneffekte und Lifecycle

useEffect ermoglicht Seiteneffekte in Funktionskomponenten.

import { useEffect, useState } from 'react';

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState(null);

  // Runs when userId changes (componentDidMount + componentDidUpdate)
  useEffect(() => {
    let cancelled = false;
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) setUser(data);
      });

    // Cleanup function (componentWillUnmount)
    return () => { cancelled = true; };
  }, [userId]); // dependency array

  return <div>{user?.name}</div>;
}

// Run once on mount
useEffect(() => {
  console.log('Component mounted');
  return () => console.log('Component unmounted');
}, []); // empty dependency array

// Run on every render (rarely needed)
useEffect(() => {
  console.log('Component rendered');
}); // no dependency array

useContext: Kontext konsumieren

useContext abonniert React-Kontext ohne verschachtelte Consumer-Komponenten.

import { createContext, useContext, useState } from 'react';

// 1. Create a context with a default value
interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | null>(null);

// 2. Create a provider component
function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. Consume context with useContext
function ThemeButton() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('Must be inside ThemeProvider');

  return (
    <button onClick={ctx.toggleTheme}>
      Current: {ctx.theme}
    </button>
  );
}

// 4. Custom hook for cleaner usage
function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
  return ctx;
}

useMemo: Teure Berechnungen memoisieren

useMemo cached das Ergebnis teurer Berechnungen zwischen Re-Renders.

import { useMemo, useState } from 'react';

function ExpensiveList({ items, filter }: { items: Item[]; filter: string }) {
  // Only recalculates when items or filter changes
  const filteredItems = useMemo(() => {
    console.log('Filtering...');
    return items.filter(item =>
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);

  // Memoize a sorted copy
  const sortedItems = useMemo(() => {
    return [...filteredItems].sort((a, b) => a.name.localeCompare(b.name));
  }, [filteredItems]);

  return (
    <ul>
      {sortedItems.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

// Do NOT overuse useMemo — only for truly expensive computations
// Simple operations do not need memoization
const total = useMemo(() => items.reduce((sum, i) => sum + i.price, 0), [items]);

useCallback: Funktionen memoisieren

useCallback gibt eine memoisierte Version einer Callback-Funktion zuruck.

import { useCallback, useState, memo } from 'react';

// Child component wrapped in memo — only re-renders if props change
const SearchInput = memo(({ onSearch }: { onSearch: (q: string) => void }) => {
  console.log('SearchInput rendered');
  return <input onChange={e => onSearch(e.target.value)} />;
});

function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  // Without useCallback, a new function is created every render
  // causing SearchInput to re-render unnecessarily
  const handleSearch = useCallback((q: string) => {
    setQuery(q);
    fetch(`/api/search?q=${q}`)
      .then(res => res.json())
      .then(setResults);
  }, []); // stable reference

  return (
    <div>
      <SearchInput onSearch={handleSearch} />
      <ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
    </div>
  );
}

useRef: Veranderliche Referenzen

useRef gibt ein veranderliches Ref-Objekt zuruck.

import { useRef, useEffect, useState } from 'react';

function TextInputWithFocus() {
  // DOM reference
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus(); // auto-focus on mount
  }, []);

  return <input ref={inputRef} placeholder="Auto-focused" />;
}

function StopWatch() {
  const [time, setTime] = useState(0);
  const intervalRef = useRef<NodeJS.Timeout | null>(null);

  const start = () => {
    intervalRef.current = setInterval(() => {
      setTime(t => t + 1);
    }, 1000);
  };

  const stop = () => {
    if (intervalRef.current) clearInterval(intervalRef.current);
  };

  // Track previous value
  const prevTimeRef = useRef(time);
  useEffect(() => { prevTimeRef.current = time; });

  return (
    <div>
      <p>Time: {time}s (prev: {prevTimeRef.current}s)</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

useReducer: Komplexe State-Logik

useReducer ist eine Alternative zu useState fur komplexe State-Logik.

import { useReducer } from 'react';

interface State {
  count: number;
  step: number;
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset' }
  | { type: 'setStep'; payload: number };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'decrement':
      return { ...state, count: state.count - state.step };
    case 'reset':
      return { count: 0, step: 1 };
    case 'setStep':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <input
        type="number"
        value={state.step}
        onChange={e => dispatch({ type: 'setStep', payload: Number(e.target.value) })}
      />
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

Custom Hooks: Wiederverwendbare Logik

Custom Hooks extrahieren Komponentenlogik in wiederverwendbare Funktionen.

// useLocalStorage — persist state to localStorage
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

// useDebounce — debounce a rapidly changing value
function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debounced;
}

// useFetch — generic data fetching
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(data => { if (!cancelled) { setData(data); setLoading(false); } })
      .catch(err => { if (!cancelled) { setError(err); setLoading(false); } });
    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}

// Usage
function App() {
  const [name, setName] = useLocalStorage('name', '');
  const debouncedName = useDebounce(name, 300);
  const { data, loading } = useFetch<User[]>(`/api/search?q=${debouncedName}`);
}

Regeln der Hooks

Zwei wesentliche Regeln fur Hooks.

  • Hooks nur auf der obersten Ebene aufrufen.
  • Hooks nur aus React-Funktionen aufrufen.
// WRONG — Hook inside a condition
function Bad({ isLoggedIn }) {
  if (isLoggedIn) {
    const [user, setUser] = useState(null); // breaks Hook order
  }
}

// CORRECT — condition inside the Hook
function Good({ isLoggedIn }) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    if (isLoggedIn) fetchUser().then(setUser);
  }, [isLoggedIn]);
}

Haufige Fallstricke und Losungen

Veraltete Closures in useEffect

Wenn State in useEffect ohne korrekte Dependencies referenziert wird.

// BUG: stale closure
function Timer() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => {
      console.log(count); // always logs 0 (stale!)
      setCount(count + 1); // always sets to 1
    }, 1000);
    return () => clearInterval(id);
  }, []); // count is missing from dependencies
}

// FIX: use functional update
useEffect(() => {
  const id = setInterval(() => {
    setCount(prev => prev + 1); // always uses latest value
  }, 1000);
  return () => clearInterval(id);
}, []); // safe with functional update

Endlosschleifen mit useEffect

State in useEffect ohne korrekte Dependencies setzen verursacht Endlosschleifen.

// BUG: infinite loop
useEffect(() => {
  setCount(count + 1); // triggers re-render, which runs effect again
}); // no dependency array = runs every render

// FIX: add dependency array
useEffect(() => {
  if (count < 10) setCount(count + 1);
}, [count]); // only runs when count changes

Objekt-/Array-Dependencies

Objekte und Arrays werden per Referenz verglichen.

// BUG: new object every render
function App() {
  const options = { page: 1, limit: 10 }; // new ref each render
  useEffect(() => {
    fetchData(options);
  }, [options]); // runs every render!
}

// FIX: useMemo to stabilize the reference
function App() {
  const options = useMemo(() => ({ page: 1, limit: 10 }), []);
  useEffect(() => {
    fetchData(options);
  }, [options]); // stable reference
}

Haufig gestellte Fragen

Was sind React Hooks?

Funktionen, die React-Features in funktionalen Komponenten ermoglichen.

Unterschied zwischen useMemo und useCallback?

useMemo memoisiert einen Wert, useCallback memoisiert eine Funktion.

Wann useReducer statt useState?

Bei komplexer State-Logik oder mehreren Unterwerten.

Hooks in Klassenkomponenten?

Nein, nur in funktionalen Komponenten.

Endlosschleifen mit useEffect vermeiden?

Immer das korrekte Dependency-Array angeben.

React Hooks sind unverzichtbar fur moderne React-Entwicklung.

War das hilfreich?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Partner Picks

Sponsor this article

Place your product next to this developer topic with tracked clicks.

Ask about article sponsorship

This site uses cookies for analytics and to display ads. By continuing to browse, you agree. Privacy Policy