Eigene Hooks & die Regeln der Hooks
Logik teilen – ohne Komponenten zu vererben
Ein eigener Hook ist einfach eine Funktion, die mit „use“ beginnt und andere Hooks benutzt. Er teilt Code – niemals Werte: jede Komponente bekommt ihre eigene Kopie.
Sobald dieselbe Kombination aus State und Effect zum zweiten Mal auftaucht, kannst du sie in eine eigene Funktion ziehen. Mehr ist ein „eigener Hook“ nicht: eine normale Funktion, deren Name mit use beginnt und die selbst Hooks benutzt.
Die zwei Regeln
1. Hooks nur auf der obersten Ebene aufrufen
-> nicht in if, nicht in for, nicht nach einem frühen return
(Ausnahme: das neue use aus React 19)
2. Hooks nur aus React-Funktionen aufrufen
-> Komponenten (Großbuchstabe) oder eigene Hooks (useXyz) Der Grund: React merkt sich Hooks über die Reihenfolge, nicht über Namen. Wird ein useState beim zweiten Render übersprungen, verrutscht die ganze Liste – und dein Zähler bekommt plötzlich den Wert des Eingabefelds.
if (!user) return null;
const [name, setName] = useState(""); // wird manchmal übersprungen const [name, setName] = useState("");
if (!user) return null; Faustregel: erst alle Hooks, dann die Bedingungen. Oder die bedingte Logik in eine eigene Komponente auslagern.
Einen eigenen Hook schreiben
export function useLocalStorage<T>(key: string, start: T) {
const [wert, setWert] = useState<T>(() => {
try {
const roh = localStorage.getItem(key);
return roh ? (JSON.parse(roh) as T) : start;
} catch {
return start;
}
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(wert));
}, [key, wert]);
return [wert, setWert] as const; // as const -> Tupel statt Array
}
// Benutzung fühlt sich an wie useState:
const [theme, setTheme] = useLocalStorage("theme", "hell"); Rufen zwei Komponenten useLocalStorage("theme", …) auf, haben beide eigene States. Sie sehen die Änderungen der jeweils anderen nicht. Willst du geteilten Zustand, brauchst du Context oder einen externen Store.
// 1) vorherigen Wert merken
export function usePrevious<T>(wert: T) {
const ref = useRef<T | undefined>(undefined);
useEffect(() => { ref.current = wert; }, [wert]);
return ref.current;
}
// 2) entprellen
export function useDebounced<T>(wert: T, ms = 300) {
const [spaet, setSpaet] = useState(wert);
useEffect(() => {
const id = setTimeout(() => setSpaet(wert), ms);
return () => clearTimeout(id);
}, [wert, ms]);
return spaet;
}
// 3) Umschalter
export function useToggle(start = false) {
const [an, setAn] = useState(start);
const umschalten = useCallback(() => setAn((a) => !a), []);
return [an, umschalten] as const;
} - +
Dieselbe Kombination aus State + Effect taucht zum zweiten Mal auf
- +
Eine Komponente ist voller Verkabelung und man sieht die Ausgabe nicht mehr
- +
Du willst Logik testen können, ohne Markup zu rendern
- −
Einen Hook für eine einzige Zeile bauen
- −
Reine Berechnungen ohne Hooks – das ist eine normale Funktion
- −
Einen Hook, der zehn unzusammenhängende Dinge tut
useChatVerbindung(raumId) sagt mehr als useEffectWrapper. Ein guter Hook-Name beschreibt, was er für dich erledigt – so wie useState nicht useMemoryCell heißt.
Sitzt das schon?
4 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.
Jetzt selbst schreiben
Zwei Stellen im Code machen dasselbe: einen Wahrheitswert umschalten. Zieh die Logik in einen eigenen Hook useToggle und benutze ihn zweimal – die beiden Schalter müssen unabhängig bleiben.
- □
useTogglegibt[wert, umschalten]zurück - □Beide Schalter lassen sich getrennt umschalten
- □Der Hook wird exportiert und heißt
useToggle
import { useState } from "react"; // TODO: useToggle hier bauen und exportieren export function useToggle(start = false) { return [start, () => {}] as const; } export default function App() { const [a, umA] = useToggle(); const [b, umB] = useToggle(true); return ( <div style={{ fontFamily: "system-ui", padding: 16 }}> <button onClick={umA} data-testid="a">A: {String(a)}</button>{" "} <button onClick={umB} data-testid="b">B: {String(b)}</button> </div> ); }