React hooky prakticky: useState, useEffect, useRef a vlastní hooky
Praktický průvodce React hooky s příklady z reálných aplikací, častými chybami v useEffect a návodem, jak napsat vlastní hook.
Hooky jsou základ moderního Reactu. Dokumentace je vysvětluje dobře, ale v praxi se nejvíc chyb dělá v detailech: zastaralé hodnoty, nekonečné smyčky v useEffect nebo zbytečné rerendery. Projdeme hooky na konkrétních příkladech a ukážeme, kde se nejčastěji chybuje.
useState: stav komponenty
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Kliknuto {count}×
</button>
);
}
Dvě pravidla, která ušetří hodiny ladění:
1. Když nová hodnota závisí na předchozí, použijte funkci. setCount(count + 1) třikrát za sebou v jednom handleru zvýší hodnotu jen o jedna, protože count je v rámci renderu konstanta. setCount(c => c + 1) funguje vždy správně.
2. Stav neměňte přímo. U objektů a polí vytvářejte nové kopie:
setUser(u => ({ ...u, name: 'Jana' }));
setItems(items => [...items, newItem]);
setItems(items => items.filter(i => i.id !== id));
items.push(x); setItems(items) nespustí rerender, protože React porovná referenci a ta je stejná.
Drahý výpočet počátečního stavu předejte jako funkci, aby se nespouštěl při každém renderu: useState(() => JSON.parse(localStorage.getItem('cart') ?? '[]')).
useEffect: synchronizace s vnějším světem
useEffect slouží k synchronizaci komponenty se systémem mimo React – API, časovače, event listenery, DOM knihovny. Není to „místo, kam dát kód, který se má spustit”.
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
Funkce vrácená z efektu je cleanup – React ji zavolá před dalším spuštěním efektu a při odpojení komponenty. Bez ní vám listenery a intervaly zůstanou viset.
Načítání dat bez race condition
useEffect(() => {
const controller = new AbortController();
setLoading(true);
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then(r => r.json())
.then(data => setUser(data))
.catch(err => {
if (err.name !== 'AbortError') setError(err);
})
.finally(() => setLoading(false));
return () => controller.abort();
}, [userId]);
Když se userId rychle změní, starý požadavek se zruší a jeho odpověď nepřepíše novější data. V React 18+ ve vývojovém režimu se StrictMode efekty spouští dvakrát právě proto, abyste chybějící cleanup odhalili.
Pro větší aplikace zvažte knihovnu jako TanStack Query, která řeší cache, opakování a deduplikaci požadavků za vás.
Kdy useEffect nepotřebujete
- Odvozené hodnoty počítejte přímo při renderu:
const fullName = first + ' ' + last;– žádný stav ani efekt. - Reakce na akci uživatele patří do event handleru, ne do efektu, který sleduje stav.
- Reset stavu při změně props – místo efektu dejte komponentě
key={userId}.
Pole závislostí
Do pole patří všechno z komponenty, co efekt používá. Nainstalujte eslint-plugin-react-hooks a pravidlo react-hooks/exhaustive-deps vás upozorní. Pokud linter chce přidat funkci, která způsobuje nekonečnou smyčku, přesuňte ji dovnitř efektu nebo mimo komponentu.
useRef: hodnota, která nespouští render
function SearchBox() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;
}
useRef má dvě použití: odkaz na DOM prvek a uchování hodnoty mezi rendery bez rerenderu (ID časovače, předchozí hodnota, počítadlo). Změna ref.current komponentu nepřekreslí – pokud se má hodnota zobrazit, patří do stavu.
useMemo a useCallback: až když je důvod
const filtered = useMemo(
() => products.filter(p => p.name.includes(query)),
[products, query]
);
const handleSelect = useCallback(id => setSelected(id), []);
Memoizace má smysl, když je výpočet opravdu drahý, nebo když předáváte funkci či objekt komponentě obalené React.memo nebo do pole závislostí efektu. Obalovat vše „pro jistotu” kód zhoršuje čitelnost a nic nezrychlí. Pokud používáte React Compiler (stabilní od roku 2025), dělá většinu memoizace automaticky.
Vlastní hook: znovupoužitelná logika
Vlastní hook je obyčejná funkce, jejíž název začíná use a která volá jiné hooky:
function useLocalStorage(key, initial) {
const [value, setValue] = useState(() => {
try {
const stored = localStorage.getItem(key);
return stored !== null ? JSON.parse(stored) : initial;
} catch {
return initial;
}
});
useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch {
/* plné úložiště nebo privátní režim */
}
}, [key, value]);
return [value, setValue];
}
// použití
const [theme, setTheme] = useLocalStorage('theme', 'light');
Každá komponenta, která hook zavolá, dostane vlastní nezávislý stav. Hooky sdílí logiku, ne data.
Další užitečný příklad – debounce pro vyhledávání:
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const t = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(t);
}, [value, delay]);
return debounced;
}
Pravidla hooků
- Volejte je jen na nejvyšší úrovni komponenty nebo vlastního hooku – ne v podmínkách, cyklech ani po
return. - Volejte je jen z React komponent nebo vlastních hooků.
React rozlišuje hooky podle pořadí volání, proto podmíněné volání rozbije stav.
Rychlá diagnostika
| Příznak | Pravděpodobná příčina |
|---|---|
| Nekonečná smyčka požadavků | objekt/funkce v závislostech vzniká při každém renderu |
| Zobrazuje se stará hodnota | chybějící závislost nebo stale closure v intervalu |
| Warning o aktualizaci odpojené komponenty / zmatená data | chybí cleanup nebo AbortController |
| Seznam se nepřekreslí | mutace pole místo nové kopie |
Když se zaseknete na konkrétní komponentě, nahrajte ji do projektu v ColdHost AI a nechte si ji v režimu Programování projít – AI umí soubor rovnou upravit a změny pak exportujete nebo pošlete jako pull request na GitHub.