Zum Inhalt springen
React Anfänger 30 min

Eigene Hooks erstellen

Lerne, wie du eigene React Hooks erstellst. Extrahiere wiederverwendbare Logik und halte deine Komponenten schlank.

Aktualisiert:

Eigene Hooks erstellen

Custom Hooks sind das maechtigste Abstraktionswerkzeug in React. Sie erlauben dir, wiederverwendbare Logik aus Komponenten zu extrahieren und in verschiedenen Komponenten zu teilen – ohne den Code zu duplizieren.

Was sind Custom Hooks?

Ein Custom Hook ist eine JavaScript-Funktion, deren Name mit use beginnt und die andere Hooks aufrufen kann:

// Ein einfacher Custom Hook
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  function increment() { setCount(prev => prev + 1); }
  function decrement() { setCount(prev => prev - 1); }
  function reset() { setCount(initialValue); }

  return { count, increment, decrement, reset };
}

// Verwendung in einer Komponente
function CounterApp() {
  const { count, increment, decrement, reset } = useCounter(10);

  return (
    <div>
      <p>Zaehler: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Regeln fuer Custom Hooks

  1. Der Name muss mit use beginnen
  2. Custom Hooks koennen andere Hooks aufrufen (useState, useEffect, etc.)
  3. Jede Komponente, die den Hook nutzt, bekommt ihren eigenen State
// Zwei Zaehler sind voellig unabhaengig!
function App() {
  const counter1 = useCounter(0);
  const counter2 = useCounter(100);

  return (
    <div>
      <p>Zaehler 1: {counter1.count}</p>
      <p>Zaehler 2: {counter2.count}</p>
    </div>
  );
}

Praktische Custom Hooks

useLocalStorage

Speichere State automatisch im LocalStorage:

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const stored = localStorage.getItem(key);
      return stored ? JSON.parse(stored) : initialValue;
    } catch {
      return initialValue;
    }
  });

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

  return [value, setValue];
}

// Verwendung
function Settings() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  const [fontSize, setFontSize] = useLocalStorage('fontSize', 16);

  return (
    <div>
      <select value={theme} onChange={(e) => setTheme(e.target.value)}>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
      </select>
      <input
        type="range"
        min="12"
        max="24"
        value={fontSize}
        onChange={(e) => setFontSize(Number(e.target.value))}
      />
      <p style={{ fontSize }}>Vorschau-Text</p>
    </div>
  );
}

useToggle

Ein einfacher Toggle fuer Booleans:

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  function toggle() { setValue(prev => !prev); }
  function setTrue() { setValue(true); }
  function setFalse() { setValue(false); }

  return { value, toggle, setTrue, setFalse };
}

// Verwendung
function App() {
  const modal = useToggle(false);
  const sidebar = useToggle(true);

  return (
    <div>
      <button onClick={sidebar.toggle}>
        Sidebar {sidebar.value ? 'schliessen' : 'oeffnen'}
      </button>
      <button onClick={modal.setTrue}>Modal oeffnen</button>

      {modal.value && (
        <div className="modal">
          <p>Ich bin ein Modal!</p>
          <button onClick={modal.setFalse}>Schliessen</button>
        </div>
      )}
    </div>
  );
}

useFetch

Daten laden mit Loading- und Error-State:

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    async function fetchData() {
      setLoading(true);
      setError(null);

      try {
        const response = await fetch(url, { signal: controller.signal });
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const json = await response.json();
        setData(json);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    }

    fetchData();

    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

// Verwendung - so einfach!
function UserList() {
  const { data: users, loading, error } = useFetch('/api/users');

  if (loading) return <p>Laden...</p>;
  if (error) return <p>Fehler: {error}</p>;

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

useWindowSize

Fenstergroesse reaktiv verfolgen:

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 ResponsiveLayout() {
  const { width } = useWindowSize();

  return (
    <div>
      {width > 768 ? (
        <DesktopLayout />
      ) : (
        <MobileLayout />
      )}
      <p>Breite: {width}px</p>
    </div>
  );
}

useDebounce

Verzoegere einen Wert (ideal fuer Sucheingaben):

function useDebounce(value, delay = 300) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

// Verwendung
function SearchBar() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);
  const { data: results } = useFetch(
    debouncedQuery ? `/api/search?q=${debouncedQuery}` : null
  );

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Suchen..."
      />
      {results && (
        <ul>
          {results.map(r => <li key={r.id}>{r.title}</li>)}
        </ul>
      )}
    </div>
  );
}

useOnClickOutside

Erkenne Klicks ausserhalb eines Elements:

function useOnClickOutside(ref, handler) {
  useEffect(() => {
    function handleClick(event) {
      if (ref.current && !ref.current.contains(event.target)) {
        handler();
      }
    }

    document.addEventListener('mousedown', handleClick);
    return () => document.removeEventListener('mousedown', handleClick);
  }, [ref, handler]);
}

// Verwendung
function Dropdown() {
  const [isOpen, setIsOpen] = useState(false);
  const dropdownRef = useRef(null);

  useOnClickOutside(dropdownRef, () => setIsOpen(false));

  return (
    <div ref={dropdownRef}>
      <button onClick={() => setIsOpen(!isOpen)}>Menue</button>
      {isOpen && (
        <ul style={{
          position: 'absolute',
          background: 'white',
          border: '1px solid #ddd',
          borderRadius: '8px',
          listStyle: 'none',
          padding: '0.5rem 0'
        }}>
          <li style={{ padding: '0.5rem 1rem' }}>Option 1</li>
          <li style={{ padding: '0.5rem 1rem' }}>Option 2</li>
          <li style={{ padding: '0.5rem 1rem' }}>Option 3</li>
        </ul>
      )}
    </div>
  );
}

Hooks organisieren

Lege deine Custom Hooks in einem eigenen Ordner ab:

src/
├── hooks/
│   ├── useLocalStorage.js
│   ├── useFetch.js
│   ├── useToggle.js
│   ├── useDebounce.js
│   └── useWindowSize.js
├── components/
└── App.jsx

Uebungen

  1. Erstelle einen useForm-Hook, der Formular-State, Change-Handler und Reset-Funktion bereitstellt
  2. Baue einen useMediaQuery-Hook, der prueft, ob ein CSS Media Query zutrifft
  3. Erstelle einen useInterval-Hook, der ein Intervall mit Start/Stop-Kontrolle bereitstellt

Was kommt als Naechstes?

Du kannst jetzt eigene Hooks erstellen und Logik wiederverwenden. Im naechsten Kapitel lernst du CSS in React – verschiedene Strategien, um deine Komponenten zu stylen.

Zusammenfassung

  • Custom Hooks beginnen mit use und koennen andere Hooks aufrufen
  • Sie extrahieren wiederverwendbare Logik aus Komponenten
  • Jede Komponente bekommt ihren eigenen, unabhaengigen State
  • Beliebte Custom Hooks: useLocalStorage, useFetch, useDebounce, useToggle
  • Organisiere Hooks in einem hooks/-Ordner

Pro-Tipp: Bevor du einen eigenen Hook schreibst, pruefe ob es bereits eine Loesung gibt. Bibliotheken wie usehooks-ts oder react-use bieten dutzende getestete und optimierte Hooks. Aber das Erstellen eigener Hooks zu ueben ist trotzdem wertvoll, um das Konzept zu verinnerlichen!

Zurück zum React Kurs