# 23 • React - hooks

> React hooks: useId, useState, useRef, useEffect
> 

> **Formát:** 30 min praktická úloha, 15 min obhajoba + teorie. Podle informací z minulých let bude praktika **doplnit `useEffect()` do existujícího projektu**. Připraveno na to. Hlavní focus teorie: **`useEffect`** (závislosti, cleanup, časté chyby).
> 

---

# Část 1: Teorie

### Co jsou hooks a proč existují

**Hook** je speciální funkce začínající `use`, která dává **funkcionálním komponentám** schopnosti, které dříve uměly jen třídní komponenty (stav, lifecycle, refy a další).

```tsx
import { useState, useEffect, useRef, useId } from 'react';
```

**Bez hooků** by funkcionální komponenta byla jen čistá funkce vracející JSX. Neuměla by si pamatovat hodnoty mezi rendery, sledovat změny ani komunikovat s DOM.

**Hooks byly přidány v React 16.8 (únor 2019)** a postupně vytlačily třídní komponenty. Dnes je standardní psát všechno funkcionálně.

---

### Pravidla hooků (důležité, často chyták)

1. **Volej je jen na nejvyšší úrovni komponenty**, ne v `if`, `for`, `while` ani uvnitř funkcí
2. **Volej je jen z React komponent nebo vlastních hooků** (funkce začínající `use`)
3. **Pořadí volání musí být stejné při každém renderu**, React je interně identifikuje pořadím, ne názvem

```tsx
// ❌ ŠPATNĚ
function Komponenta({ prihlasen }: { prihlasen: boolean }) {
    if (prihlasen) {
        const [data, setData] = useState(null);    // podmíněný hook!
    }
}

// ✓ SPRÁVNĚ
function Komponenta({ prihlasen }: { prihlasen: boolean }) {
    const [data, setData] = useState(null);        // vždy zavolán
    if (prihlasen) {
        // logika podmíněná, ale hook ne
    }
}
```

> **Proč to platí**: React si vede interní seznam hooks pro každou komponentu. Identifikuje je **podle pořadí**, ne názvu. Pokud by `useState` byl volán někdy a někdy ne, React by si spletl, který stav patří kterému hooku, a všechno by se rozsypalo.
> 

> **Lint plugin `eslint-plugin-react-hooks`** tohle hlídá automaticky, ale ne každý projekt ho má.
> 

---

### `useState`: lokální stav komponenty

Drží hodnotu, která **přežije mezi rendery**. Změna spustí re-render.

```tsx
import { useState } from 'react';

function Pocitadlo() {
    const [pocet, setPocet] = useState<number>(0);

    return (
        <div>
            <p>Klikuto: {pocet}×</p>
            <button onClick={() => setPocet(pocet + 1)}>+1</button>
            <button onClick={() => setPocet(0)}>Reset</button>
        </div>
    );
}
```

### Funkční update (závisí na předchozí hodnotě)

```tsx
// ❌ Riziko race condition při více rychlých kliknutích
setPocet(pocet + 1);

// ✓ Garantovaně použije aktuální hodnotu
setPocet(prev => prev + 1);
```

Když máš třeba 3 rychlá kliknutí během jednoho renderu, `pocet + 1` třikrát použije stejnou (zastaralou) hodnotu. Funkční update řeší tím, že React předá aktuální stav.

### Typované state

```tsx
const [text, setText] = useState('');                              // string (odvozeno)
const [pocet, setPocet] = useState(0);                             // number (odvozeno)
const [aktiv, setAktiv] = useState(false);                         // boolean (odvozeno)
const [polozky, setPolozky] = useState<string[]>([]);              // explicitní pole
const [user, setUser] = useState<User | null>(null);               // nullable
const [filter, setFilter] = useState<'vse' | 'aktivni'>('vse');    // union type
```

### Immutability

State **nikdy nemodifikuj přímo**, vždy přes setter, vždy s novou referencí:

```tsx
// ❌ React nezjistí změnu (stejná reference)
polozky.push('nová');
setPolozky(polozky);

// ✓ Nové pole
setPolozky([...polozky, 'nová']);

// ❌ Modifikace objektu
obj.jmeno = 'axo';
setObj(obj);

// ✓ Nový objekt
setObj({ ...obj, jmeno: 'axo' });
```

---

### `useEffect`: vedlejší efekty (klíčové)

Spustí kód **po vykreslení** komponenty. Slouží pro "věci mimo React":

- API volání (fetch, axios)
- Timery a intervaly (setInterval, setTimeout)
- Manipulace s DOM (focus, scroll, document.title)
- Předplatné na události (window.addEventListener)
- Čtení/zápis do `localStorage`
- Synchronizace s externími systémy

### Základní syntaxe

```tsx
useEffect(() => {
    // KÓD EFEKTU: spustí se po renderu
    console.log('Komponenta vykreslena');

    return () => {
        // CLEANUP (volitelný): spustí se před dalším efektem nebo při unmount
        console.log('Cleanup');
    };
}, [zavislosti]);   // pole závislostí: určuje KDY se efekt spustí
```

### Závislosti: kdy se efekt spustí

| Pole závislostí | Kdy se efekt spustí |
| --- | --- |
| `[]` (prázdné) | **Jen jednou** po prvním renderu (mount) |
| `[a, b]` | Po prvním renderu **+ pokaždé když se změní `a` nebo `b`** |
| (vynecháno) | **Po každém renderu**, pozor: může způsobit nekonečnou smyčku! |

```tsx
// Jen při mount
useEffect(() => {
    console.log('Mount');
}, []);

// Při mount + při změně x
useEffect(() => {
    console.log('x se změnil:', x);
}, [x]);

// Po každém renderu (zřídka chcete)
useEffect(() => {
    console.log('Každý render');
});
```

---

### Typické use cases `useEffect`

### A) Načtení dat z API

```tsx
function ProfilUzivatele({ id }: { id: number }) {
    const [user, setUser] = useState<User | null>(null);

    useEffect(() => {
        fetch(`/api/users/${id}`)
            .then(res => res.json())
            .then(data => setUser(data));
    }, [id]);   // znovu při změně id

    if (!user) return <p>Načítám...</p>;
    return <h1>{user.jmeno}</h1>;
}
```

### B) Změna `document.title`

```tsx
useEffect(() => {
    document.title = `Kliknutí: ${pocet}`;
}, [pocet]);
```

### C) Timer / interval (s cleanupem!)

```tsx
useEffect(() => {
    const id = setInterval(() => {
        console.log('Tick');
    }, 1000);

    return () => clearInterval(id);   // CLEANUP, jinak by tikala víckrát
}, []);
```

### D) Event listener na window (s cleanupem!)

```tsx
useEffect(() => {
    const handler = () => setSirka(window.innerWidth);
    window.addEventListener('resize', handler);

    return () => window.removeEventListener('resize', handler);
}, []);
```

### E) Synchronizace s `localStorage`

```tsx
// Načtení při mount
useEffect(() => {
    const saved = localStorage.getItem('text');
    if (saved) setText(saved);
}, []);

// Uložení při každé změně
useEffect(() => {
    localStorage.setItem('text', text);
}, [text]);
```

---

### Cleanup function

Vrácená funkce v `useEffect` se spustí:

1. **Před dalším spuštěním efektu** (závislost se změnila)
2. **Při odmontování komponenty** (zmizí z DOM)

Použití: zrušit timer, odregistrovat listener, zrušit fetch. **Předejít memory leakům**.

### Race condition s AbortController

> Klasický problém: uživatel rychle změní `id`, první fetch ještě běží, druhý začne, vrátí se v opačném pořadí, vidíš data od staré requestu. `AbortController` to řeší.
> 

```tsx
useEffect(() => {
    const ctrl = new AbortController();

    fetch(`/api/users/${id}`, { signal: ctrl.signal })
        .then(res => res.json())
        .then(setUser)
        .catch(err => {
            if (err.name !== 'AbortError') console.error(err);
        });

    return () => ctrl.abort();   // zruší fetch pokud id se změní nebo komponenta zmizí
}, [id]);
```

---

### Časté chyby s `useEffect`

| Chyba | Důsledek | Řešení |
| --- | --- | --- |
| Chybějící závislost | Zastaralé hodnoty, "stale closure" | Přidat do pole závislostí |
| Vynechané pole závislostí | Spustí se po každém renderu | Přidat `[]` nebo `[deps]` |
| `setState` v efektu bez podmínky a bez deps | **Nekonečná smyčka** | Přidat deps + podmínku |
| Forgotten cleanup u timeru/listeneru | Memory leak | Vrátit funkci z efektu |
| Async funkce přímo jako argument | Chyba (efekt nechce Promise) | Wrap: `useEffect(() => { (async () => {...})() }, [])` |
| Závislost na objektu/poli | Spustí se při každém renderu (nová reference) | useMemo nebo primitivní hodnoty v deps |

```tsx
// ❌ Chybí závislost (eslint-plugin-react-hooks to chytí)
useEffect(() => {
    console.log(pocet);
}, []);

// ❌ Nekonečná smyčka
useEffect(() => {
    setPocet(pocet + 1);    // bez závislostí → po každém renderu
});

// ❌ Async funkce přímo
useEffect(async () => {     // chyba! useEffect nechce Promise
    const data = await fetch(...);
}, []);

// ✓ Závislosti kompletní
useEffect(() => {
    console.log(pocet);
}, [pocet]);

// ✓ Async wrapper
useEffect(() => {
    (async () => {
        const data = await fetch(...);
    })();
}, []);
```

---

### Kdy NEPOUŽÍVAT useEffect (anti-patterny)

> Tahle sekce v běžné teorii často chybí, ale zkoušející ji ocení. Hodí se i do budoucnosti do praxe.
> 

`useEffect` má tendenci být **overused**. React tým má dokumentaci ["You Might Not Need an Effect"](https://react.dev/learn/you-might-not-need-an-effect).

### Anti-pattern 1: Derived state z props

```tsx
// ❌ Zbytečný useEffect
function Komponenta({ jmeno }: { jmeno: string }) {
    const [velkePisme, setVelkePisme] = useState('');

    useEffect(() => {
        setVelkePisme(jmeno.toUpperCase());
    }, [jmeno]);

    return <h1>{velkePisme}</h1>;
}

// ✓ Prostě počítej během renderu
function Komponenta({ jmeno }: { jmeno: string }) {
    const velkePisme = jmeno.toUpperCase();   // přepočítává se každý render, ale je to OK
    return <h1>{velkePisme}</h1>;
}
```

### Anti-pattern 2: Reset stavu při změně props

```tsx
// ❌ useEffect na reset
useEffect(() => {
    setSelectedId(null);
}, [userId]);

// ✓ Použij key prop (komponenta se znovu mountuje)
<ProfilDetail key={userId} userId={userId} />
```

### Anti-pattern 3: Vypočítat odvozená data s memoization

```tsx
// ❌ useEffect pro filtrování
const [filtrovane, setFiltrovane] = useState([]);
useEffect(() => {
    setFiltrovane(seznam.filter(x => x.aktivni));
}, [seznam]);

// ✓ useMemo (nebo prostě v renderu)
const filtrovane = useMemo(() => seznam.filter(x => x.aktivni), [seznam]);
```

> **Pravidlo**: pokud lze hodnotu spočítat **z props/stavu** během renderu, nepotřebuješ useEffect. useEffect je pro **synchronizaci s externími systémy** (DOM, API, timer, browser API).
> 

---

### React StrictMode: useEffect běží 2x v dev

> Drobnost, na kterou každý nový React developer narazí.
> 

V React 18+ při zapnutém `<React.StrictMode>` (defaultně v Vite/Next.js dev modu) React **úmyslně spouští useEffect dvakrát**, aby odhalil bugy s missing cleanupem.

```tsx
useEffect(() => {
    console.log('Effect run');     // v dev modu se vypíše DVAKRÁT

    return () => console.log('Cleanup');
}, []);

// V dev console:
// "Effect run"
// "Cleanup"
// "Effect run"

// V production běží jen jednou.
```

Klasický bug, který tohle odhalí: bez cleanup u `setInterval` se v dev modu rovnou rozjedou **dva intervaly**. V production sice jen jeden, ale stejně by tikalo špatně při změně závislostí.

**Závěr**: vždy piš správný cleanup. StrictMode tě k tomu donutí.

---

### `useRef`: odkaz, který přežije rendery

Vrací **mutovatelný kontejner** (`{ current: ... }`), jehož změna **NEspouští re-render**. Dvě hlavní použití:

### A) Přístup k DOM elementu

```tsx
import { useRef, useEffect } from 'react';

function AutoFocusInput() {
    const inputRef = useRef<HTMLInputElement>(null);

    useEffect(() => {
        inputRef.current?.focus();   // při mount nastaví focus na input
    }, []);

    return <input ref={inputRef} />;
}
```

Klíčové: `ref={inputRef}` napojí ref na DOM element. `inputRef.current` je pak ten samotný `<input>` HTML element.

### B) Uchování hodnoty bez re-renderu

```tsx
function Casovac() {
    const [cas, setCas] = useState(0);
    const intervalRef = useRef<number | null>(null);   // uchová ID intervalu

    const start = () => {
        intervalRef.current = window.setInterval(() => {
            setCas(prev => prev + 1);
        }, 1000);
    };

    const stop = () => {
        if (intervalRef.current !== null) {
            clearInterval(intervalRef.current);
            intervalRef.current = null;
        }
    };

    return (
        <div>
            <p>Čas: {cas}s</p>
            <button onClick={start}>Start</button>
            <button onClick={stop}>Stop</button>
        </div>
    );
}
```

> ID intervalu nepotřebuje vyvolat re-render při změně, jen si ho potřebujeme zapamatovat. Proto `useRef` místo `useState`.
> 

### `useState` vs `useRef`

|  | `useState` | `useRef` |
| --- | --- | --- |
| Spouští re-render | Ano | Ne |
| Hodí se pro | UI hodnoty (text, počet, pole) | Timery, DOM odkazy, neviditelné hodnoty |
| Přístup | `state` | `ref.current` |
| Změna | `setState(...)` | `ref.current = ...` (přímo) |
| Persist mezi rendery | Ano | Ano |

---

### `useId`: unikátní ID pro accessibility

Vygeneruje **stabilní unikátní ID**, ideální pro propojení `<label>` a `<input>` v re-použitelných komponentách.

```tsx
import { useId } from 'react';

function FormPole({ label }: { label: string }) {
    const id = useId();   // např. ":r1:"

    return (
        <div>
            <label htmlFor={id}>{label}</label>
            <input id={id} type="text" />
        </div>
    );
}

// Použití: každá instance dostane vlastní unikátní id
<FormPole label="Jméno" />
<FormPole label="Email" />
```

> **Nepoužívej `useId` pro `key`** v `map()`! Má sloužit pro accessibility/HTML id, ne pro identifikaci prvků v listu. Pro `key` použij vlastní stabilní ID z dat.
> 

> **Proč ne `Math.random()`?** Náhodné ID by se měnilo při každém renderu, label by ztratil propojení s inputem. `useId` je stabilní mezi rendery a navíc safe pro SSR (server a klient generují stejné ID).
> 

---

### Lifecycle funkcionální komponenty s hooks

```
              1× MOUNT
              ─────────
[Komponenta vznikne]
       │
       ▼
  Render JSX
       │
       ▼
  useEffect(() => {...}, [])  ← spustí se JEDNOU
       │
       ▼
  ┌──────────────────────────┐
  │  Komponenta žije         │
  │                          │
  │  setState() → re-render  │
  │                          │
  │  useEffect(..., [dep])   │
  │  spustí se když se dep   │
  │  změnila                 │
  │  ────────────────        │
  │  Cleanup z předchozí     │
  │  iterace efektu          │
  │  (před novým spuštěním)  │
  └──────────────────────────┘
       │
       ▼
[Komponenta zmizí]
       │
       ▼
  Cleanup funkce (return v useEffect)  ← UNMOUNT
```

Třídní ekvivalent (pro představu, nepoužívat):

- `useEffect(() => {}, [])` ≈ `componentDidMount`
- Cleanup function ≈ `componentWillUnmount`
- `useEffect(() => {}, [x])` ≈ `componentDidUpdate` (s kontrolou `x`)

---

### Vlastní hook: sdílení logiky mezi komponentami

Funkce začínající `use*`, která uvnitř volá další hooks.

```tsx
import { useState, useEffect } from 'react';

// Vlastní hook pro localStorage state
function useLocalStorage<T>(key: string, vychozi: T) {
    const [hodnota, setHodnota] = useState<T>(() => {
        const ulozeno = localStorage.getItem(key);
        return ulozeno ? JSON.parse(ulozeno) : vychozi;
    });

    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(hodnota));
    }, [key, hodnota]);

    return [hodnota, setHodnota] as const;
}

// Použití: funguje úplně stejně jako useState
const [text, setText] = useLocalStorage<string>('muj-text', '');
```

> **`as const`** říká TypeScriptu, že vrácený tuple má pevné typy `[T, Setter<T>]`, ne obecný array.
> 

> Vlastní hooks **musí začínat `use`**, aby React linter rozpoznal, že jsou hook a aplikoval pravidla.
> 

---

### Další běžné hooky (přehled)

| Hook | K čemu |
| --- | --- |
| `useState` | Lokální stav |
| `useEffect` | Vedlejší efekty (API, timer, DOM) |
| `useRef` | DOM odkaz, mutable hodnota bez re-renderu |
| `useId` | Unikátní ID pro accessibility |
| `useContext` | Čtení sdíleného Context |
| `useMemo` | Zapamatovaný výpočet (memoizace) |
| `useCallback` | Zapamatovaná funkce (referenční stabilita) |
| `useReducer` | Komplexní stav (alternativa k useState, jako Redux mini) |
| `useTransition` | Markování updatů jako "non-urgent" (React 18+) |
| `useDeferredValue` | Odložená hodnota pro performance (React 18+) |

---

### Časté chyby s hooks

| Chyba | Důsledek | Řešení |
| --- | --- | --- |
| Hook v `if` nebo cyklu | Crash při re-renderu | Vždy na top-level |
| Zapomenutá závislost v `useEffect` | Stale closure | Přidat do deps array |
| Vynechané pole závislostí | Spustí se po každém renderu | `[]` nebo `[deps]` |
| `setState` v efektu bez podmínky | Nekonečná smyčka | Přidat deps + podmínku |
| Forgotten cleanup u timeru | Memory leak, dva timery v StrictMode | Vrátit cleanup |
| `useRef` pro UI hodnotu | Nepřekreslí se | Použít `useState` |
| `useState` pro DOM ref | Nadbytečné re-rendery | Použít `useRef` |
| useEffect pro derived state | Zbytečný re-render | Spočítat v renderu nebo `useMemo` |
| `useId` jako `key` v `map` | Špatné chování | Použít vlastní ID z dat |
| `Math.random()` v renderu | Změna při každém renderu | `useRef` nebo `useId` |

---

# Část 2: Praktická úloha

### Co může praktická úloha obsahovat

Podle informací z minulých let bude úloha typicky **doplnit useEffect (a další hooks) do existujícího starter projektu**. To znamená:

- **Vyplnit kostru** s několika TODO komentáři
- **Různé typy useEffect** s různými závislostmi (`[]`, `[x]`, `[obj]`)
- **Cleanup funkce** (clearTimeout, removeEventListener)
- **useRef** pro auto-focus
- **useId** pro form accessibility
- **useState** pro form data

### Příklad zadání: Formulář pro kavárnu

Vytvoříš objednávkový formulář kavárny, který procvičí všechny 4 hooky:

- **`useState`** drží formulářová pole
- **`useId`** propojí `<label>` a `<input>`
- **`useRef`** pro automatický focus na první input
- **`useEffect`** s 5 různými použitími (focus, fetch, auto-save, load draft, document.title)

### Setup

```bash
npm create vite@latest kavarna -- --template react-ts
cd kavarna
npm install
npm run dev
```

### Datový model

```tsx
type Objednavka = {
    jmeno: string;
    napoj: string;
    mnozstvi: number;
    poznamka: string;
};
```

### Řešení: kompletní `App.tsx`

```tsx
import { useState, useEffect, useRef, useId } from 'react';

type Objednavka = {
    jmeno: string;
    napoj: string;
    mnozstvi: number;
    poznamka: string;
};

const STORAGE_KEY = 'objednavka-draft';

export default function App() {
    // Stav formuláře (jeden useState pro celý objekt)
    const [obj, setObj] = useState<Objednavka>({
        jmeno: '',
        napoj: '',
        mnozstvi: 1,
        poznamka: ''
    });

    // Seznam dostupných nápojů (načteme z fake API)
    const [napoje, setNapoje] = useState<string[]>([]);

    // Stav pro počet uložení (do title)
    const [pocetOdeslani, setPocetOdeslani] = useState(0);

    // Ref pro automatický focus na první input
    const jmenoRef = useRef<HTMLInputElement>(null);

    // useId pro accessibility (label htmlFor + input id)
    const jmenoId = useId();
    const napojId = useId();
    const mnozstviId = useId();
    const poznamkaId = useId();

    // ═══ TODO 1: useEffect: focus na první input PŘI MOUNT ═══
    useEffect(() => {
        jmenoRef.current?.focus();
    }, []);

    // ═══ TODO 2: useEffect: načti napoje z fake API PŘI MOUNT ═══
    useEffect(() => {
        const id = setTimeout(() => {
            setNapoje(['Espresso', 'Cappuccino', 'Latte', 'Čaj']);
        }, 500);

        // CLEANUP: zruš timer pokud komponenta zmizí
        return () => clearTimeout(id);
    }, []);

    // ═══ TODO 3: useEffect: načti DRAFT z localStorage PŘI MOUNT ═══
    useEffect(() => {
        const saved = localStorage.getItem(STORAGE_KEY);
        if (saved) {
            try {
                setObj(JSON.parse(saved));
            } catch {
                // poškozený JSON ignoruj
            }
        }
    }, []);

    // ═══ TODO 4: useEffect: auto-save objednávky do localStorage ═══
    // POZOR: spustí se i při mountu, takže poprvé zapíše defaultní hodnoty.
    // To je OK (přepíšeme draft který se mezitím načetl v TODO 3,
    // ale TODO 3 se spustí dřív, takže nedojde ke ztrátě).
    useEffect(() => {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(obj));
    }, [obj]);

    // ═══ TODO 5: useEffect: document.title podle počtu odeslání ═══
    useEffect(() => {
        document.title = `Kavárna (odesláno: ${pocetOdeslani})`;
    }, [pocetOdeslani]);

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        console.log('Objednávka:', obj);
        setPocetOdeslani(prev => prev + 1);

        // Reset formuláře
        setObj({ jmeno: '', napoj: '', mnozstvi: 1, poznamka: '' });
        localStorage.removeItem(STORAGE_KEY);
    };

    return (
        <form onSubmit={handleSubmit} style={{ maxWidth: 500, margin: '2rem auto' }}>
            <h1>Objednávka: Kavárna ☕</h1>

            <div style={{ marginBottom: '1rem' }}>
                <label htmlFor={jmenoId}>Jméno:</label>
                <input
                    ref={jmenoRef}
                    id={jmenoId}
                    value={obj.jmeno}
                    onChange={e => setObj({ ...obj, jmeno: e.target.value })}
                    required
                />
            </div>

            <div style={{ marginBottom: '1rem' }}>
                <label htmlFor={napojId}>Nápoj:</label>
                <select
                    id={napojId}
                    value={obj.napoj}
                    onChange={e => setObj({ ...obj, napoj: e.target.value })}
                    required
                >
                    <option value="">-- Vyberte --</option>
                    {napoje.map(n => (
                        <option key={n} value={n}>{n}</option>
                    ))}
                </select>
            </div>

            <div style={{ marginBottom: '1rem' }}>
                <label htmlFor={mnozstviId}>Množství:</label>
                <input
                    id={mnozstviId}
                    type="number"
                    min={1}
                    value={obj.mnozstvi}
                    onChange={e => setObj({ ...obj, mnozstvi: Number(e.target.value) })}
                />
            </div>

            <div style={{ marginBottom: '1rem' }}>
                <label htmlFor={poznamkaId}>Poznámka:</label>
                <textarea
                    id={poznamkaId}
                    value={obj.poznamka}
                    onChange={e => setObj({ ...obj, poznamka: e.target.value })}
                />
            </div>

            <button type="submit">Objednat</button>
        </form>
    );
}
```

### Co se v řešení děje

**5 různých useEffect s různými závislostmi:**

| # | Účel | Závislosti | Cleanup |
| --- | --- | --- | --- |
| 1 | Focus na input | `[]` | Ne |
| 2 | Fetch nápojů | `[]` | `clearTimeout` |
| 3 | Load draft z localStorage | `[]` | Ne |
| 4 | Auto-save do localStorage | `[obj]` | Ne |
| 5 | Update document.title | `[pocetOdeslani]` | Ne |

**TODO 1 (focus)**: `useRef<HTMLInputElement>(null)` vytvoří ref. `<input ref={jmenoRef}>` ho napojí na DOM element. `useEffect` s `[]` (jen při mount) zavolá `.focus()`.

**TODO 2 (fetch napojů)**: Simulace fetch přes `setTimeout` (500ms delay). Cleanup s `clearTimeout` zruší timer, pokud uživatel zavře stránku během 500ms (jinak by `setNapoje` zavolaný na unmounted komponentě hodil warning).

**TODO 3 (load draft)**: Při mount zkusíme načíst draft z localStorage. Try-catch chrání proti poškozenému JSON. **Pořadí spuštění je důležité**: TODO 3 musí být před TODO 4 v kódu, aby se draft načetl dřív než ho TODO 4 přepíše defaultními hodnotami.

**TODO 4 (auto-save)**: Závislost `[obj]` znamená, že efekt se spustí pokaždé, když se objekt změní (uživatel napsal něco do inputu). Také se spustí při mount (uloží defaultní hodnoty). Při refreshi se draft natáhne z TODO 3.

**TODO 5 (document.title)**: Změní `document.title` po odeslání. Vidí se v záložce prohlížeče.

**Form pattern**: jeden velký `useState` objekt místo 4 samostatných stavů. Update přes `setObj({ ...obj, jmeno: e.target.value })` (spread + override jednoho pole).

**Accessibility**: `useId` generuje stabilní ID pro každý input. `<label htmlFor={id}>` a `<input id={id}>` musí mít stejné ID, aby kliknutí na label fokuslo input (a screen reader věděl, co je popisek čeho).

---

### Bonusy

### Bonus A: useEffect s cleanup pro window resize

```tsx
const [sirka, setSirka] = useState(window.innerWidth);

useEffect(() => {
    const handler = () => setSirka(window.innerWidth);
    window.addEventListener('resize', handler);

    return () => window.removeEventListener('resize', handler);
}, []);

// V JSX:
<p>Šířka okna: {sirka}px</p>
```

### Bonus B: Vlastní hook `useLocalStorage`

```tsx
function useLocalStorage<T>(key: string, vychozi: T): [T, (val: T) => void] {
    const [hodnota, setHodnota] = useState<T>(() => {
        const ulozeno = localStorage.getItem(key);
        if (!ulozeno) return vychozi;
        try {
            return JSON.parse(ulozeno);
        } catch {
            return vychozi;
        }
    });

    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(hodnota));
    }, [key, hodnota]);

    return [hodnota, setHodnota];
}

// Použití: sjednotí TODO 3 + TODO 4
const [obj, setObj] = useLocalStorage<Objednavka>('objednavka-draft', {
    jmeno: '', napoj: '', mnozstvi: 1, poznamka: ''
});
// TODO 3 a TODO 4 můžeš smazat, hook to dělá za tebe
```

### Bonus C: useEffect se závislostí na obj.napoj

```tsx
useEffect(() => {
    if (obj.napoj) {
        console.log('Vybráno:', obj.napoj);
    }
}, [obj.napoj]);   // spustí se jen při změně napoje, ne ostatních polí
```

### Bonus D: AbortController pro fetch napojů (race condition prevention)

> Pokud bys nahradil setTimeout reálným fetch:
> 

```tsx
useEffect(() => {
    const ctrl = new AbortController();

    fetch('/api/napoje', { signal: ctrl.signal })
        .then(r => r.json())
        .then(setNapoje)
        .catch(err => {
            if (err.name !== 'AbortError') console.error(err);
        });

    return () => ctrl.abort();
}, []);
```

---

## Část 3: Tipy pro obhajobu

### Co u obhajoby říct

> *"V zadání jsem doplnil 5 useEffect hooks s různými závislostmi. Pro mount-only chování použiju prázdné pole závislostí []. Pro reakci na změnu specifické hodnoty dám tu hodnotu do pole. Cleanup u setTimeout je důležitý kvůli StrictMode: React 18 v dev modu spouští efekt 2x pro odhalení memory leaků. Bez clearTimeout by mi šly dva timery zároveň. Pro focus jsem použil useRef typu HTMLInputElement, který se napojil přes ref atribut na input. useRef se hodí pro DOM přístup, protože nezpůsobí re-render. Pro accessibility jsem použil useId, který vygeneruje stabilní unikátní ID pro propojení label htmlFor a input id. Math.random() by tu nefungoval, protože by se ID měnilo při každém renderu a label by ztratil propojení. Stav formuláře držím v jednom useState objektu, update přes spread operator pro immutability."*
> 

### Klíčové pojmy pro teorii

| Pojem | Rychlá odpověď |
| --- | --- |
| **Hook** | Funkce začínající `use`, dává funkcionálním komponentám stav a lifecycle |
| **Pravidla hooků** | Top-level only, jen v komponentě nebo vlastním hooku, stejné pořadí |
| **`useState`** | Lokální stav, změna spustí re-render |
| **`useEffect`** | Vedlejší efekty (API, timer, DOM, listener) |
| **Pole závislostí** | Řídí kdy efekt běží: `[]` = jednou, `[x]` = při změně x, vynecháno = každý render |
| **Cleanup funkce** | Vrácená funkce v useEffect, spustí se před dalším efektem nebo unmountu |
| **`useRef`** | Mutable container `{current}` bez re-renderu, DOM odkaz |
| **`useId`** | Stabilní unikátní ID pro accessibility |
| **StrictMode** | Dev-only, spouští useEffect 2x pro odhalení bugů s cleanupem |
| **Vlastní hook** | Funkce začínající `use`, sdílí logiku mezi komponentami |
| **Funkční update** | `setState(prev => prev + 1)` místo `setState(state + 1)` |
| **Immutability** | Vytvořit nový objekt/pole, nemodifikovat přímo |
| **Stale closure** | Funkce v efektu si pamatuje starou hodnotu (chybí v deps) |
| **AbortController** | API pro zrušení fetch requestů, prevence race condition |
| **Derived state** | Vypočítané z props/state v renderu (ne přes useEffect) |
| **`useState` vs `useRef`** | useState = UI, re-render. useRef = DOM/neviditelné, bez re-renderu |

### Časté chytáky

| Otázka | Odpověď |
| --- | --- |
| *Co jsou hooks?* | Funkce začínající `use`, dávají funkcionálním komponentám stav, lifecycle a další schopnosti. |
| *Pravidla hooků?* | Vždy na top-level (ne v if/cyklu), jen v komponentě nebo vlastním hooku. React je identifikuje pořadím. |
| *Co `useEffect` s `[]`?* | Spustí se jen jednou po prvním renderu (mount). Pro inicializaci, fetch dat. |
| *Co `useEffect` s `[x]`?* | Spustí se při mount + při každé změně `x`. |
| *Co `useEffect` bez pole?* | Spustí se po každém renderu. Pozor, často způsobí nekonečnou smyčku. |
| *K čemu cleanup?* | Spustí se před dalším efektem nebo při unmount. Pro zrušení timerů, listenerů, fetch. Předchází memory leakům. |
| *Rozdíl `useState` a `useRef`?* | useState spouští re-render. useRef ne, je pro DOM odkazy a hodnoty mimo render. |
| *Proč fokus přes `useRef` v `useEffect`?* | Při renderu ještě DOM neexistuje. useEffect se spustí po renderu, kdy už `ref.current` je nastaven. |
| *K čemu `useId`?* | Pro stabilní unikátní ID v re-usable komponentách (label htmlFor + input id). |
| *Proč ne `Math.random()` místo `useId`?* | Náhodné ID se mění při každém renderu, label ztratí propojení. useId je stabilní. |
| *Co je StrictMode?* | Dev-only wrapper, který spouští useEffect 2x. Odhaluje bugy s missing cleanupem. |
| *Co je stale closure?* | Efekt si pamatuje starou hodnotu proměnné, protože není v deps. Klasický bug. |
| *Kdy NEPOUŽÍVAT useEffect?* | Pro derived state z props (spočítej v renderu), pro reset stavu při změně props (použij `key`). |

### Časté chyby v praktické úloze

- Chybějící `[]` u useEffectu, který má běžet jen jednou (nekonečná smyčka)
- Chybějící cleanup u `setTimeout`/`setInterval` (memory leak, dva timery v StrictMode)
- `useRef` použitý mimo `useEffect` (DOM ještě neexistuje při prvním renderu)
- `Math.random()` pro `id` (neguje accessibility, mění se při renderu)
- `setObj(...)` co modifikuje původní objekt (`obj.jmeno = '...'`)
- `useId` použit jako `key` v `map` (špatná funkce)
- Závislost na objektu `[obj]` který má novou referenci každý render (efekt běží pořád)
- Async funkce přímo jako `useEffect(async () => {...}, [])` (chyba, useEffect nečeká Promise)
- Forgotten `e.preventDefault()` v form `onSubmit` (form reloaduje stránku)
- Zapomenutý `try-catch` u `JSON.parse(localStorage.getItem(...))` (crash při poškozených datech)
- `useState` pro DOM ref (zbytečné re-rendery)
- `useRef` pro UI hodnotu, kterou chci zobrazit (změna se neprojeví)
- Více useEffect, které by mohly být v jednom (kosmetika)
- Chybějící order: load draft → auto-save (jinak se default přepíše dřív než načteš)