Eigene Hooks erstellen
Lerne, wie du eigene React Hooks erstellst. Extrahiere wiederverwendbare Logik und halte deine Komponenten schlank.
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
- Der Name muss mit
usebeginnen - Custom Hooks koennen andere Hooks aufrufen (useState, useEffect, etc.)
- 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
- Erstelle einen
useForm-Hook, der Formular-State, Change-Handler und Reset-Funktion bereitstellt - Baue einen
useMediaQuery-Hook, der prueft, ob ein CSS Media Query zutrifft - 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
useund 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!