Custom Hooks in React: Eigene Logik wiederverwenden
Du kennst schon useState und useEffect – jetzt lernst du, wie du daraus eigene Hooks baust und deine Logik sauber zwischen Komponenten teilst.
Wenn du schon eine Weile mit React arbeitest, kennst du useState und useEffect. Irgendwann fällt dir auf, dass du dieselbe Logik – etwa das Laden von Daten oder das Umschalten eines Zustands – in mehreren Komponenten wiederholst. Genau hier kommen Custom Hooks ins Spiel: eigene Funktionen, die React-Hooks verwenden und die du wie jeden anderen Hook einsetzt. In diesem Beitrag lernst du, wie du eigene Hooks schreibst und damit deinen Code deutlich aufräumst.
Was ist ein Custom Hook?
Ein Custom Hook ist einfach eine JavaScript-Funktion, deren Name mit use beginnt und die intern andere Hooks aufruft. Mehr steckt technisch nicht dahinter. Das use-Präfix ist keine Kosmetik: React nutzt es, um zu prüfen, ob du die Regeln der Hooks einhältst.
Ein Custom Hook bündelt Zustand und Verhalten an einer Stelle. Statt in jeder Komponente dieselben useState- und useEffect-Zeilen zu schreiben, packst du sie in eine Funktion und rufst sie mit einer Zeile auf. Wichtig: Zwei Komponenten, die denselben Hook benutzen, teilen sich nicht den Zustand – jeder Aufruf bekommt seine eigene, unabhängige Instanz.
Dein erster Hook: useToggle
Fangen wir klein an. Ein Umschalter zwischen true und false braucht man ständig – für Modals, Menüs oder Dark-Mode-Buttons. Als Custom Hook sieht das so aus:
import { useState, useCallback } from 'react';
function useToggle(startwert = false) {
const [an, setAn] = useState(startwert);
const umschalten = useCallback(() => setAn(v => !v), []);
return [an, umschalten];
}Wir geben ein Array zurück – genau wie useState selbst. Dadurch fühlt sich die Nutzung vertraut an, und du kannst die Variablen beim Aufruf frei benennen:
function Menue() {
const [offen, umschalten] = useToggle();
return (
<div>
<button onClick={umschalten}>
{offen ? 'Schließen' : 'Öffnen'}
</button>
{offen && <nav>Navigation …</nav>}
</div>
);
}Die gesamte Umschalt-Logik lebt jetzt an einer Stelle. Brauchst du sie in zehn Komponenten, importierst du einfach zehnmal useToggle.
Daten laden mit useFetch
Ein klassischer Anwendungsfall ist das Laden von Daten aus einer API. Ohne Custom Hook wiederholst du in jeder Komponente die drei Zustände für Daten, Ladeanzeige und Fehler. Ausgelagert wird das viel angenehmer:
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [laedt, setLaedt] = useState(true);
const [fehler, setFehler] = useState(null);
useEffect(() => {
let aktiv = true;
setLaedt(true);
fetch(url)
.then(res => {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(json => {
if (aktiv) {
setData(json);
setFehler(null);
}
})
.catch(err => {
if (aktiv) setFehler(err.message);
})
.finally(() => {
if (aktiv) setLaedt(false);
});
return () => { aktiv = false; };
}, [url]);
return { data, laedt, fehler };
}Die Variable aktiv und die Aufräumfunktion am Ende des useEffect verhindern, dass wir Zustand setzen, nachdem die Komponente bereits verschwunden ist. Das erspart dir Warnungen und subtile Fehler. In der Komponente bleibt nur noch die reine Anzeige übrig:
function Profil({ id }) {
const { data, laedt, fehler } = useFetch(`/api/user/${id}`);
if (laedt) return <p>Lädt …</p>;
if (fehler) return <p>Fehler: {fehler}</p>;
return <h2>{data.name}</h2>;
}Zustand dauerhaft speichern: useLocalStorage
Custom Hooks lassen sich auch kombinieren. Dieser Hook verhält sich wie useState, speichert den Wert aber zusätzlich im localStorage, sodass er einen Seiten-Reload übersteht:
import { useState, useEffect } from 'react';
function useLocalStorage(key, startwert) {
const [wert, setWert] = useState(() => {
const gespeichert = localStorage.getItem(key);
return gespeichert !== null ? JSON.parse(gespeichert) : startwert;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(wert));
}, [key, wert]);
return [wert, setWert];
}Beachte die Funktion, die wir an useState übergeben: Diese Lazy-Initialisierung sorgt dafür, dass der localStorage nur beim ersten Rendern gelesen wird, nicht bei jedem. Die Nutzung ist so einfach wie bei useState:
function Einstellungen() {
const [thema, setThema] = useLocalStorage('thema', 'hell');
return (
<select value={thema} onChange={e => setThema(e.target.value)}>
<option value="hell">Hell</option>
<option value="dunkel">Dunkel</option>
</select>
);
}Die Regeln für Custom Hooks
Damit deine Hooks zuverlässig funktionieren, gelten dieselben Regeln wie für die eingebauten Hooks:
- Name mit
use: Nur so erkennt React (und dein Linter), dass es sich um einen Hook handelt. - Nur auf oberster Ebene aufrufen: Keine Hooks in Schleifen, Bedingungen oder verschachtelten Funktionen. React verlässt sich darauf, dass die Reihenfolge der Hook-Aufrufe bei jedem Rendern gleich bleibt.
- Nur in Komponenten oder anderen Hooks aufrufen: Nicht in gewöhnlichen Funktionen.
- Ein Hook pro Aufgabe: Halte Hooks fokussiert. Ein Hook, der zu viel auf einmal macht, wird schnell schwer wartbar.
Ein guter Test: Wenn du Logik zwischen zwei Komponenten kopierst, ist das oft ein Signal, sie in einen Custom Hook zu verschieben.
Fazit
Custom Hooks sind eines der stärksten Werkzeuge in React, um Logik sauber zu teilen – ganz ohne zusätzliche Bibliotheken. Du baust sie aus den Hooks, die du schon kennst, und gibst ihnen einen sprechenden Namen mit use-Präfix. Fang klein an, etwa mit einem useToggle, und arbeite dich zu wiederverwendbaren Hooks wie useFetch vor. Dein Code wird lesbarer, deine Komponenten kürzer und deine Logik testbar an einer einzigen Stelle. Probier es beim nächsten Mal aus, wenn du dich beim Kopieren von useState-Zeilen ertappst.