Přeskočit na obsah
Kurz Reactu1.0.0 beta
CSEN
Kurz Reactu1.0.0 beta
CSEN
🏠 Úvod🧭 Kde začít?🔁 Opakování📄 Taháky📰 Co je novéhoℹ️ O kurzu
JavaScript0/22
TypeScript0/6

Základy

  • 01 Rychlé opakování základů0/3
  • 02 Stav a události (useState)0/3

Hooky do hloubky

  • 03 Efekty (useEffect)0/3
  • 04 Refy (useRef)0/3
  • 05 Složitější stav (useReducer)0/3
  • 06 Context0/3
  • 07 Vlastní hooky0/3

Formuláře

  • 08 Formuláře a React 19 Actions0/3

Výkon

  • 09 Výkon a renderování0/3

Vzory

  • 10 Návrhové vzory komponent0/3

Ekosystém

  • 11 TypeScript s Reactem0/3
  • 12 Routování (React Router)0/3
  • 13 Načítání dat0/3
  • 14 Správa stavu aplikace0/3

Projekt

  • 15 Závěrečný projekt: Kanban0/2

Shrnutí

  • 16 Shrnutí: principy Reactu

Tvůj účet

Ochrana osobních údajů

React 06 · Hooky do hloubky

Context

Konec prop drillingu, vzor Provider + hook a výkonnostní pasti.

Načítám lekci…

💬 Našel jsi chybu, něco ti není jasné, nebo máš nápad? Napiš mi.
← Složitější stav (useReducer)Vlastní hooky →

Než začneme: vzkaz přes celou školu, nebo rozhlas?

Ředitel školy potřebuje říct žákům ve třetím patře, že odpoledne odpadá tělocvik. Bez rozhlasu by to šlo takhle: ředitel to řekne zástupkyni, ta učiteli na chodbě, ten třídnímu učiteli, a ten konečně žákům. Zástupkyně ani učitel na chodbě tu zprávu nepotřebují – musí ji jen nést dál. A když někdo v řadě zapomene, zpráva se ztratí.

V Reactu se tomu říká prop drilling (provrtávání props). Data tečou jen shora dolů přes props, takže když je potřebuje komponenta hluboko ve stromu, musí je nést každá komponenta mezi nimi – i ta, která je vůbec nepoužívá.

Škola to řeší rozhlasem. Ředitel mluví do mikrofonu v ústředně a slyší ho každý, kdo je v budově a má reproduktor. Nikdo nic nenese. Přesně tohle je Context:

  • Provider je ústředna rozhlasu – komponenta nahoře ve stromu, která „vysílá“ hodnotu.
  • use(Context) je reproduktor – kterákoli komponenta pod Providerem si hlášení poslechne.
  • Komponenty mezi nimi o hlášení vůbec nevědí.
ℹ️ Co tě v lekci čeká
  1. Prop drilling na konkrétním příkladu.
  2. Context krok za krokem a vzor „Provider + vlastní hook“.
  3. Kdy Context použít, kdy ne – a co zkusit dřív.
  4. Výkon: proč se při změně překreslí všichni posluchači a jak tomu předejít.

1. Problém: prop drilling

Aplikace má přihlášeného uživatele. Jeho avatar se ukazuje v postranním panelu, který je uvnitř layoutu, který je uvnitř aplikace:

function App() {
const [user, setUser] = useState<User>(…)
return <Layout user={user} />
}
function Layout({ user }: { user: User }) { // nepotřebuje, jen nese dál
return <Sidebar user={user} />
}
function Sidebar({ user }: { user: User }) { // nepotřebuje, jen nese dál
return <Avatar user={user} />
}
function Avatar({ user }: { user: User }) { // konečně ho použije
return <img src={user.photo} alt={user.name} />
}

Proč to vadí:

  • Layout a Sidebar musí znát typ User a mít prop, který nepotřebují.
  • Když bude uživatele potřebovat ještě tlačítko v hlavičce, přidáváš prop do další řady komponent.
  • Přejmenování nebo změna typu znamená upravit všechny komponenty v řadě.

Dvě nebo tři úrovně jsou v pořádku – props jsou jasné a čitelné. Problém začíná, když se stejná data nesou přes hodně vrstev k hodně místům.

2. Context krok za krokem

Postavíme rozhlas pro barevné téma (světlé/tmavé), které chce znát spousta komponent.

Krok 1: postav ústřednu

const ThemeContext = createContext<{ theme: Theme; toggle: () => void } | null>(null)

createContext vytvoří „kanál“. Hodnota v závorce (null) je to, co uslyší komponenta, která je mimo dosah jakéhokoli Provideru – reproduktor bez signálu.

Krok 2: zapni vysílání

function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>('light')
const toggle = () => setTheme((t) => (t === 'light' ? 'dark' : 'light'))
return <ThemeContext value={{ theme, toggle }}>{children}</ThemeContext>
}
// Někde nahoře v aplikaci:
<ThemeProvider>
<Layout />
</ThemeProvider>
  • Provider je obyčejná komponenta, která má stav (téma) a vysílá ho spolu s funkcí na změnu. Context sám nic nepamatuje – jen doručuje.
  • <ThemeContext value={…}> je v Reactu 19 zápis Provideru. Ve starším kódu uvidíš <ThemeContext.Provider value={…}>.
  • Vysílání slyší jen komponenty uvnitř – všechno, co je v children, a jejich potomci.

Krok 3: reproduktor jako vlastní hook

function useTheme() {
const ctx = use(ThemeContext)
if (!ctx) throw new Error('useTheme musí být uvnitř <ThemeProvider>')
return ctx
}
// Kdekoli hluboko ve stromu:
function ThemedButton() {
const { theme, toggle } = useTheme()
return <button className={theme} onClick={toggle}>Přepnout</button>
}

Proč vlastní hook a ne rovnou use(ThemeContext) v každé komponentě? Když někdo zapomene obalit aplikaci Providerem, bez kontroly by dostal null a o pár řádků dál nesrozumitelnou chybu „cannot read properties of null“. S hookem dostane jasnou zprávu, co je špatně. Navíc komponenty nemusí vědět, jak je kontext uvnitř postavený.

❓ use(ThemeContext), nebo useContext(ThemeContext)? Jaký je rozdíl?

Pro čtení kontextu dělají totéž. useContext je starší hook, use přišel v Reactu 19 a umí navíc dvě věci: dá se zavolat i uvnitř if (výjimka z pravidel hooků) a umí číst i Promise (lekce o načítání dat). V novém kódu stačí use, ve starším uvidíš useContext a nemusíš ho přepisovat.

❓ Co když je nad komponentou Providerů víc – třeba jeden vnořený v druhém?

Reproduktor poslouchá nejbližší ústřednu nad sebou. Toho se dá využít: celá aplikace je ve světlém tématu a jen jeden panel obalíš dalším <ThemeContext value={dark}>. Všechno uvnitř panelu uslyší tmavé, zbytek světlé. Různé kontexty (téma, uživatel, jazyk) se navzájem neovlivňují, každý má vlastní kanál.

Co se stane po kliknutí na „Přepnout“

  1. toggle() zavolá setTheme v Provideru. Změnil se stav Provideru.
  2. Provider se překreslí a vyšle nový objekt { theme: "dark", toggle }.
  3. React najde všechny komponenty, které kontext poslouchají, a překreslí je s novou hodnotou. Layout mezi nimi se nemusí překreslovat kvůli kontextu – nic z něj nečte.
Ukázka

Téma přes Context + vlastní hook

Co vidíš: Malá aplikace: Layout obsahuje Sidebar a panel „Obsah“. Panely a tlačítko čtou téma přes useTheme(). Layout ani Sidebar nedostávají žádný prop o tématu.

Vyzkoušej:

  1. Klikni na Přepnout z postranního panelu. Oba panely ztmavnou – i „Obsah“, který s postranním panelem nemá nic společného.
  2. Klikni znovu. Všechno se vrátí do světlého tématu.
  3. Otevři kód a najdi komponentu Layout. Neobsahuje ani slovo o tématu – přesto jsou obě její části v souladu.

Co z toho plyne: Jedna hodnota ve stavu Provideru, mnoho posluchačů kdekoli pod ním, a nikdo mezi nimi ji nemusí nést.

Načítám interaktivní část…

✨ Vzor, který používej vždy
  1. createContext<T | null>(null)
  2. Komponenta XxxProvider, která drží stav
  3. Hook useXxx(), který mimo Provider vyhodí srozumitelnou chybu
  4. Kontext samotný neexportuj, exportuj jen Provider a hook

3. Kdy Context použít (a kdy ne)

Rozhlasem se hlásí věci, které zajímají celou školu: konec vyučování, požární poplach. Kdyby se rozhlasem řešilo, kdo komu půjčil gumu, nikdo by se neslyšel. Context se hodí na data, která potřebuje hodně míst a která se mění zřídka.

✅ Dobrý kandidát❌ Špatný kandidát
Téma, jazyk, formát měnyData, která se mění při každém stisku klávesy
Přihlášený uživatel, oprávněníServerová data (to je práce pro TanStack Query)
Služby: toasty, modaly, analytikaStav, který stačí předat o 1–2 úrovně props
Stav složitého widgetu (Tabs, Accordion) sdílený mezi jeho částmiGlobální „úložiště všeho“

Než sáhneš po kontextu, zkus kompozici

Někdy jde o to, že zprávu nesou lidé, kteří ji vůbec nést nemusí. Místo rozhlasu stačí posadit žáka přímo do ředitelny. V Reactu: místo abys data protlačoval přes Layout, předej mu hotový prvek, který už data má:

// ❌ Layout nese user jen proto, aby ho předal dál
<Layout user={user} />
// ✅ Avatar vytvoříš tam, kde user je. Layout jen určí, KDE se ukáže.
<Layout sidebar={<Avatar user={user} />} />
function Layout({ sidebar }: { sidebar: ReactNode }) {
return <div className="layout"><aside>{sidebar}</aside>…</div>
}

Layout teď o uživateli neví nic – a nepotřebuje k tomu ani Context. Tohle je sloty z lekce o základech.

4. Výkon: kdo se překreslí?

Rozhlas má jednu nevýhodu: když se hlášení změní, poslouchat musí všichni, i když se jich týká jen kousek. Hlásí se „tělocvik odpadá a v jídelně jsou knedlíky“ – a kvůli knedlíkům zpozorní i třída, která tělocvik vůbec nemá.

Context funguje stejně. Když Provider vyšle novou hodnotu (React to pozná porovnáním reference), překreslí se všichni, kdo kontext čtou – i ti, kteří používají jen část hodnoty.

function AppProvider({ children }: { children: ReactNode }) {
const [count, setCount] = useState(0)
// Při každé změně count vznikne NOVÝ objekt → všichni posluchači se překreslí
return <AppContext value={{ user: 'Jana', count, inc }}>{children}</AppContext>
}
function UserBadge() {
const { user } = use(AppContext) // čte jen user…
return <span>{user}</span> // …ale překreslí se i při každé změně count
}

Řešení: místo jednoho kanálu použij víc kanálů – kontext pro každou věc, která se mění zvlášť.

Ukázka

Jeden velký vs. rozdělené kontexty

Co vidíš: Dvě stejné dvojice komponent: jméno uživatele a počítadlo. Vlevo čtou obě z jednoho kontextu, vpravo z dvou oddělených. Každá karta žlutě blikne, když se překreslí. Všechny jsou obalené v memo, takže kvůli rodiči se nepřekreslují.

Vyzkoušej:

  1. Klikni vlevo na +1. Bliknou obě karty – i karta se jménem, které se nezměnilo.
  2. Klikni vpravo na +1. Blikne jen počítadlo. Karta se jménem poslouchá jiný kanál, ve kterém se nic nezměnilo.
  3. Všimni si, že ani memo levé kartě se jménem nepomohl. Změna kontextu ho obchází.

Co z toho plyne: Každý posluchač se překreslí při každé změně svého kanálu. Co se mění nezávisle, dej do samostatných kontextů.

Načítám interaktivní část…

Oddělený stav a akce

Častý a velmi účinný vzor je spojit Context s reducerem z minulé lekce. Stav vysílá jeden kanál, funkci dispatch druhý. dispatch se nikdy nemění, takže komponenty, které jen posílají akce (tlačítka „Přidat“), se při změně dat nepřekreslují:

const TasksContext = createContext<Task[] | null>(null)
const TasksDispatchContext = createContext<Dispatch<Action> | null>(null)
function TasksProvider({ children }: { children: ReactNode }) {
const [tasks, dispatch] = useReducer(tasksReducer, [])
return (
<TasksContext value={tasks}>
<TasksDispatchContext value={dispatch}>{children}</TasksDispatchContext>
</TasksContext>
)
}
function AddTaskButton() {
const dispatch = useTasksDispatch() // poslouchá jen kanál, který se nemění
return <button onClick={() => dispatch({ type: 'added', text: 'Nový' })}>Přidat</button>
}
❓ Co přesně dělají useCallback a useMemo (potřebuju je ve výzvě Toasty)?

Obojí řeší stejný problém: všechno, co v těle komponenty vytvoříš ({ … }, […], () => …), vzniká při každém renderu znovu, jako nový objekt. Pro kontext to znamená „nová hodnota“, a všichni posluchači se překreslí, i když se obsah nezměnil.

// useCallback: „vrať mi pořád TUTÉŽ funkci, dokud se nezmění závislosti“
const show = useCallback((msg: string) => {
setToasts((all) => [...all, msg])
}, []) // [] = funkce se nikdy nemění
// useMemo: „vrať mi pořád TENTÝŽ výsledek, dokud se nezmění závislosti“
const api = useMemo(() => ({ show }), [show]) // objekt vznikne jen jednou
return <ToastContext value={api}>…</ToastContext> // hodnota kontextu je stabilní

useCallback(fn, deps) je jen zkratka pro useMemo(() => fn, deps). Závislosti fungují jako u efektu: když se některá změní, vznikne nová verze. Podrobně a s ukázkami je probírá lekce o výkonu. Tady stačí vědět, že ti zajistí stabilní hodnotu kontextu.

ℹ️ Další způsoby, jak zbytečné překreslení omezit
  • Stabilizuj hodnotu pomocí useMemo/useCallback, aby Provider nevysílal nový objekt při každém svém renderu (React Compiler to udělá automaticky).
  • Když potřebuješ jemné odběry („překresli mě, jen když se změní cart.count“), použij Zustand nebo Redux – umí selektory (lekce Správa stavu).

Ověř si to

Kvíz Co se stane, když komponenta volá useTheme(), ale nad ní žádný ThemeProvider není?
Kvíz Komponenta Page jen předává prop user do Header, který ho předá do UserMenu. Co zkusíš jako první?
Kvíz Jeden kontext vysílá { user, cart }. Košík se mění často. Co uděláš, aby se komponenty zobrazující jen uživatele nepřekreslovaly?

Rozcvička

Výzva

Barva z kontextu

Rozcvička

Přečti hodnotu z kontextu – bez předávání props přes Middle.

Zadání najdeš v komentářích v kódu níže – na začátku souboru a u míst označených TODO.

🧩 Odkud se berou věci, které tu nejsou definované
CSS třídy jako row, stack, card, list, btn, input nebo muted jsou hotové styly kurzu v src/styles.css (row = prvky vedle sebe, stack = pod sebou, card = rámeček, muted = šedý text).

Načítám interaktivní část…

Výzvy

První výzva procvičí kapitolu 2 – celý vzor Provider + hook. Druhá kapitolu 4: kontext jako „služba“, který vysílá jen funkci, takže komponenty, které notifikace vyvolávají, se nepřekreslují.

Výzva

Přihlášení přes Context

Lehká

Kompletní vzor Provider + hook + obsah podle role. Komponenta Page mezi nimi nesmí o uživateli nic vědět – stejně jako zástupkyně, která už nemusí nést vzkaz. Co se má dít, je v komentářích v kódu.

Zadání najdeš v komentářích v kódu níže – na začátku souboru a u míst označených TODO.

🧩 Odkud se berou věci, které tu nejsou definované
CSS třídy jako row, stack, card, list, btn, input nebo muted jsou hotové styly kurzu v src/styles.css (row = prvky vedle sebe, stack = pod sebou, card = rámeček, muted = šedý text).

Načítám interaktivní část…

Výzva

Systém notifikací

StředníBonus · zdarma po přihlášení

Kontext jako „služba“ – přesně tak fungují toasty v knihovnách jako sonner nebo react-hot-toast. Co se má dít, je v komentářích v kódu.

🔓 Bonusová výzva – zdarma po přihlášení

Střední a těžké výzvy se odemknou, jakmile se přihlásíš. Stejně jako celý kurz jsou zdarma – stačí e-mail, žádné heslo ani platba.

Jak nakládáme s tvými údaji

Shrnutí

  • Prop drilling = nošení vzkazu přes lidi, kteří ho nepotřebují.
  • Context je rozhlas: Provider vysílá, use(Ctx) poslouchá, komponenty mezi nimi o ničem nevědí.
  • Vzor: createContext(null) + Provider se stavem + hook s kontrolou. V Reactu 19 <Ctx value> a use(Ctx).
  • Context je pro data, která potřebuje hodně míst a mění se zřídka. Nejdřív zkus kompozici.
  • Změna hodnoty překreslí všechny posluchače – i přes memo. Rozděl kontexty na víc kanálů a odděl stav od dispatch.