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ÚčelAnalogie (class komponenty)
useStateLokální reaktivní stavthis.state
useEffectSide effects, async operacecomponentDidMount/Update/WillUnmount
useRefMutable reference, DOM přístupcreateRef()
useMemoMemoizace výpočtu (cache hodnoty)Manuální cache
useCallbackMemoizace funkce (stabilní reference)Manuální bind
useContextČtení Context bez prop drillingucontextType
useReducerKomplexní stav (alternativa useState)this.setState + Redux
useIdUnikátní ID pro přístupnost
📏
Pravidla hooks: Nikdy nevolat v podmínkách, cyklech nebo vnořených funkcích. Nikdy nevolat mimo React funkce. ESLint plugin eslint-plugin-react-hooks tato pravidla kontroluje automaticky.
// 01 / 08 useEffect →

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.

// component lifecycle s useEffect
🟢
Mount
⚛️
Render JSX
💫
useEffect runs
🔄
State change → re-render
🔴
Unmount → cleanup
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);
  }, []);
}
useEffect — fetch dat + timer

// 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 rule exhaustive-deps — Vždy mějte 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.
// 03 / 08 useRef →

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
}
useRef — stopky s DOM přístupem

// 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>;
});
💡
Kdy memoizovat: useMemo pro výpočty, které trvají >1ms (sorting/filtering velkých polí). useCallback pro funkce předávané do React.memo komponent nebo jako deps jiných hooks. Neobalujte vše — memoizace má vlastní overhead.
useMemo — výkonný filtr a řazení
// 05 / 08 Custom hooks →

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;
}
Custom hooks — useFetch + useDebounce

// Kvíz: Musí custom hook začínat slovem use?

// 06 / 08 Cvičení →

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.

Vaše řešení
🏅

React Hooks Master

Zvládáte useEffect, useRef, useMemo, useCallback a custom hooks.

// 07 / 08 Taháček →

// 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;
}