Hooks — přehled
Hooks (zavedeny React 16.8) umožňují používat stav a lifecycle funkce v funkčních komponentách. Nahradily class komponenty. Pravidla: volat pouze na top-level (ne v podmínkách), pouze v React komponentách nebo custom hooks.
| Hook | Účel | Analogie (class komponenty) |
|---|---|---|
useState | Lokální reaktivní stav | this.state |
useEffect | Side effects, async operace | componentDidMount/Update/WillUnmount |
useRef | Mutable reference, DOM přístup | createRef() |
useMemo | Memoizace výpočtu (cache hodnoty) | Manuální cache |
useCallback | Memoizace funkce (stabilní reference) | Manuální bind |
useContext | Čtení Context bez prop drillingu | contextType |
useReducer | Komplexní stav (alternativa useState) | this.setState + Redux |
useId | Unikátní ID pro přístupnost | — |
eslint-plugin-react-hooks tato pravidla kontroluje automaticky.useEffect — side effects
useEffect spouští vedlejší efekty po renderu — fetch dat, DOM manipulace, subscriptions, timery. React zaručuje, že efekt proběhne po aktualizaci DOM.
import { useEffect, useState } from 'react';
function Component() {
const [data, setData] = useState(null);
// 1. Bez dependency array — spustí se po KAŽDÉM renderu
useEffect(() => {
console.log('Po každém renderu');
});
// 2. Prázdné pole [] — spustí se jen při MOUNT (jednou)
useEffect(() => {
console.log('Komponenta se připojila');
// Cleanup funkce — spustí se při UNMOUNT
return () => {
console.log('Komponenta se odpojila');
};
}, []);
// 3. S dependencies — spustí se když se změní userId
useEffect(() => {
async function nacti() {
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
setData(data);
}
nacti();
}, [userId]); // ← závislosti
// 4. Cleanup — timer, subscription, event listener
useEffect(() => {
const timer = setInterval(() => setCount(c => c + 1), 1000);
// Cleanup zabrání memory leaku!
return () => clearInterval(timer);
}, []);
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
}
// Kvíz: useEffect(() => { ... }, []) — kdy se efekt spustí?
Dependency array — záludnosti
Dependency array je nejčastější zdroj bugů v React aplikacích. Pravidlo: každá hodnota použitá uvnitř efektu musí být v deps array.
// ❌ CHYBĚJÍCÍ závislost — stale closure bug
const [count, setCount] = useState(0);
useEffect(() => {
const t = setInterval(() => {
console.log(count); // Vždy vypíše 0! (stale closure)
setCount(count + 1); // ❌ count je vždy 0
}, 1000);
return () => clearInterval(t);
}, []); // ← count chybí v deps!
// ✅ Funkční update — nevyžaduje závislost
useEffect(() => {
const t = setInterval(() => {
setCount(prev => prev + 1); // ✅ vždy aktuální
}, 1000);
return () => clearInterval(t);
}, []);
// ❌ Objekt/pole v deps — nová reference každý render
useEffect(() => {
// spustí se po KAŽDÉM renderu!
}, [{ id: 1 }]); // ← nový objekt každý render
// ✅ Primitivní hodnoty nebo useMemo/useCallback
useEffect(() => {
nactiData(userId);
}, [userId]); // ← string/number je v pořádku
// ✅ useEffectEvent (React 19) — experimentální řešení stale closure
// Alternativa pro teď: useRef pro funkce
eslint-plugin-react-hooks. Pravidlo react-hooks/exhaustive-deps varuje před chybějícími závislostmi. Nikdy ho nevypínejte bez pochopení důvodu.useRef — mutable reference
useRef vrátí objekt { current: hodnota }, který přetrvá přes re-rendery a jehož změna nespustí re-render. Dvě hlavní použití: přístup k DOM elementům a ukládání mutable hodnot bez re-renderu.
import { useRef, useEffect } from 'react';
// 1. DOM reference
function InputWithFocus() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus(); // přímý DOM přístup
inputRef.current.select();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Focus</button>
</>
);
}
// 2. Mutable hodnota bez re-renderu
function Timer() {
const [count, setCount] = useState(0);
const timerRef = useRef(null); // ID intervalu
const start = () => {
if (timerRef.current) return; // už běží
timerRef.current = setInterval(
() => setCount(c => c + 1), 1000
);
};
const stop = () => {
clearInterval(timerRef.current);
timerRef.current = null;
};
useEffect(() => () => clearInterval(timerRef.current), []);
return <div>{count} <button onClick={start}>Start</button> <button onClick={stop}>Stop</button></div>;
}
// 3. Previous value pattern
function Component({ value }) {
const prevValue = useRef(value);
useEffect(() => { prevValue.current = value; });
// prevValue.current = předchozí render value
}
// Kvíz: Jaký je hlavní rozdíl mezi useRef a useState?
useMemo & useCallback
Oba hooks memoizují — cachují výsledek dokud se nezmění závislosti. Slouží k optimalizaci výkonu, ale nepřeoptimalizujte předčasně.
// useMemo — cachuje HODNOTU (výsledek výpočtu)
import { useMemo } from 'react';
function ProductList({ products, filter, sortBy }) {
// Výpočet proběhne jen když se změní products, filter nebo sortBy
const filtered = useMemo(() => {
return products
.filter(p => p.category === filter)
.sort((a, b) => a[sortBy] > b[sortBy] ? 1 : -1);
}, [products, filter, sortBy]);
return filtered.map(p => <ProductCard key={p.id} {...p} />);
}
// useCallback — cachuje FUNKCI (stabilní reference)
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Bez useCallback: nová funkce každý render → Child se vždy re-renderuje
// S useCallback: stejná reference → Child se re-renderuje jen při změně deps
const handleDelete = useCallback((id) => {
setItems(prev => prev.filter(item => item.id !== id));
}, []); // ← prázdné deps = stabilní funkce
return <Child onDelete={handleDelete} />;
}
// React.memo — skip re-render pokud se props nezměnily
const Child = React.memo(function Child({ onDelete }) {
console.log('Child render'); // bez memo: každý render Parent → render Child
return <button onClick={() => onDelete(1)}>Smazat</button>;
});
Custom hooks — znovupoužitelná logika
Custom hook je funkce začínající use, která volá jiné hooks. Umožňuje extrahovat a sdílet stavovou logiku mezi komponentami — bez HOC nebo render props.
// useFetch — generický data fetching hook
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(null);
fetch(url)
.then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return 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 };
}
// Použití — čistá a přehledná komponenta
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(
`https://api.example.com/users/${userId}`
);
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <ProfileCard user={user} />;
}
// useLocalStorage — perzistentní stav
function useLocalStorage(key, defaultValue) {
const [value, setValue] = useState(() => {
try {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : defaultValue;
} catch { return defaultValue; }
});
const setAndStore = (newValue) => {
const val = typeof newValue === 'function' ? newValue(value) : newValue;
setValue(val);
localStorage.setItem(key, JSON.stringify(val));
};
return [value, setAndStore];
}
// useDebounce — odložení hodnoty
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const t = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(t);
}, [value, delay]);
return debounced;
}
// Kvíz: Musí custom hook začínat slovem use?
Cvičení — useWindowSize + useLocalStorage
Napište dva custom hooks: useWindowSize() vrátí { width, height } a aktualizuje se při resize okna (event listener + cleanup). useLocalStorage(key, default) funguje jako useState, ale persistuje data.
React Hooks Master
Zvládáte useEffect, useRef, useMemo, useCallback a custom hooks.
// Taháček
// useEffect — 3 varianty
useEffect(() => { /* po každém renderu */ });
useEffect(() => { /* jen mount */ return () => { /* cleanup */ }; }, []);
useEffect(() => { /* při změně dep */ }, [dep]);
// useRef
const ref = useRef(null);
ref.current; // přístup k hodnotě (nemutuje render)
<input ref={ref} /> // DOM reference
// useMemo & useCallback
const value = useMemo(() => expensiveCalc(a, b), [a, b]);
const fn = useCallback((x) => doSomething(x, dep), [dep]);
// Custom hook skeleton
function useMyHook(param) {
const [state, setState] = useState(null);
useEffect(() => { /* logika */ }, [param]);
return state;
}