Lekce 2.2

React Context: Teleport pro data

V klasické komponentové architektuře proudí data jedním směrem – z rodiče do dítěte přes props. Jak ale aplikace roste, začnete řešit prop drilling: předávání údajů přes mnoho vrstev komponent, které tyto údaje samy vůbec nepotřebují.

Co se naučíte
  • Co je prop drilling a proč je problém
  • K čemu Context API slouží a k čemu ne
  • Tři kroky práce s contextem: vytvoření, provider, consumer
  • Jak zkombinovat useContext a useState pro reaktivní data
  • Jak fungují výchozí hodnoty contextu
  • Kdy Context používat a kdy zůstat u props

Problém zvaný prop drilling

Představte si, že máte URL uživatelova avataru na samém vrchu aplikace (App.js), ale komponenta, která obrázek skutečně renderuje (Avatar.js), je zanořená o deset úrovní níže v layoutech, headerech a menu.

Abyste se k ní dostali s obyčejnými props, musí každý mezilehlý komponent prop přijmout a předat dál – i když ho sám nikdy nepoužije. Přesně tomu se říká prop drilling.

React Context je vestavěné řešení této zbytečné práce. Můžete si ho představit jako teleporter: umožní vám poslat data z vrcholu stromu přímo do libovolně hluboko zanořeného komponentu a všechny mezikroky přeskočit.

Tři kroky práce s Contextem

Abyste tento „teleport“ používali efektivně, stačí zvládnout tři kroky:

  • Vytvoření contextu
  • Poskytování (broadcast) dat
  • Spotřebování (přijímání) dat

Pojďme si to projít na praktickém příkladu: uživatelská autentizace. Chceme mít profil aktuálního uživatele dostupný všude, aniž bychom ho museli ručně předávat každým layoutem.

Krok 1: Vytvoření contextu

Nejprve založíme komunikační kanál pomocí React.createContext. Typicky ho definujete v samostatném souboru a exportujete, aby ho ostatní komponenty mohly importovat a „poslouchat“.

tsx
import * as React from "react"

// Create the context.
// We export it so other components can import it to "listen" in.
const UserContext = React.createContext(null)

export default UserContext

Krok 2: Poskytování dat (broadcast)

Každý context objekt má komponentu .Provider. Cokoli předáte do jejího prop value, bude dostupné všem komponentům uvnitř Provideru, ať jsou zanořené jakkoli hluboko.

tsx
import * as React from "react"
import UserContext from "./UserContext"
import DashboardLayout from "./DashboardLayout"

export default function App() {
  // This is the data we want to teleport
  const activeUser = {
    username: "Neo_TheOne",
    role: "Admin",
    status: "Active",
    lastLogin: "2 minutes ago",
  }

  return (
    // We wrap our component tree in the Provider.
    // We pass 'activeUser' into the 'value' prop.
    <UserContext.Provider value={activeUser}>
      <DashboardLayout />
    </UserContext.Provider>
  )
}

Všimněte si, že mezilehlé komponenty jako layouty, headery nebo menu se objektu activeUser nemusí vůbec dotknout, pokud ho samy nepotřebují.

Krok 3: Spotřebování contextu

Hluboko ve stromu pak komponenta, která data skutečně potřebuje, použije React.useContext a na context se přihlásí.

tsx
import * as React from "react"
import UserContext from "./UserContext"

export default function UserProfile() {
  // Accessing the teleported data
  const user = React.useContext(UserContext)

  if (!user) return null

  return (
    <div className="profile-card">
      <h3>Welcome back, {user.username}</h3>
      <p>Role: {user.role}</p>
      <span className="badge">{user.status}</span>
    </div>
  )
}

Context vs. state: trubky vs. voda

Časté nedorozumění je, že Context je správce stavu. Není. Context je transportní mechanismus.

Pokud je Context soustava trubek, state je voda, která jimi protéká. Contextu je jedno, jestli jsou data statická (konfigurace) nebo dynamická (část stavu).

Aby byla aplikace reaktivní, kombinujete Context (trubku) s useState (zdroj vody).

Kombinace contextu s useState

Upravme náš příklad tak, aby komponenty mohly aktuálního uživatele nejen číst, ale i měnit.

tsx
import * as React from "react"
import UserContext from "./UserContext"
import DashboardLayout from "./DashboardLayout"

export default function App() {
  // 1. We manage the state here using useState
  const [user, setUser] = React.useState({
    username: "Neo_TheOne",
    status: "Online",
  })

  // 2. We teleport the state variable AND the setter function
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <DashboardLayout />
    </UserContext.Provider>
  )
}

Nyní může každý komponent, který zavolá useContext(UserContext), user jak číst, tak měnit pomocí setUser. Když se state v App změní, React strom znovu vyrenderuje a novou hodnotu pošle všem consumerům.

Výchozí hodnoty contextu

Při vytváření contextu můžete volitelně zadat výchozí hodnotu. Ta se použije pouze tehdy, když komponent context spotřebuje, ale v jeho nadřazeném stromu nenajde odpovídající Provider.

tsx
import * as React from "react"

// Default is "light"
const ThemeContext = React.createContext("light")

function ThemedButton() {
  const theme = React.useContext(ThemeContext)
  return <button className={`btn-${theme}`}>I am a {theme} button</button>
}

export default function Page() {
  return (
    <main>
      {/* 1. No Provider here: falls back to default "light" */}
      <ThemedButton />

      {/* 2. Wrapped in a Provider: uses value "dark" */}
      <ThemeContext.Provider value="dark">
        <ThemedButton />
      </ThemeContext.Provider>
    </main>
  )
}

Tento vzor je obzvlášť užitečný pro témata, lokalizaci nebo znovupoužitelné komponenty, které mají fungovat „z krabice“, ale dají se přizpůsobit obalením do Provideru.

Kdy Context použít – a kdy ne

Protože je Context mocný, svádí to použít ho úplně na všechno. Prop drilling ale sám o sobě není špatný – dělá tok dat explicitní a snadno sledovatelný.

Dobré použití Contextu

  • Data, která jsou globální pro větší část stromu (uživatel, téma, jazyk/locale)
  • Hodnoty potřebné na mnoha různých úrovních zanoření

Kdy se Contextu vyhnout

  • Potřebujete data předat jen o jednu až dvě úrovně níž – props jsou jednodušší a přehlednější.
  • Řešíte výkon: aktualizace contextu vysoko ve stromu může způsobit re‑render velkého množství consumerů.

Shrnutí

  • Context je teleport pro data, ne správce stavu.
  • Používáte tři kroky: vytvořit context, obalit část stromu Providerem a číst hodnotu pomocí useContext.
  • Kombinací Contextu a useState sdílíte reaktivní data napříč aplikací.
  • Context použijte, když je cesta přes props zbytečně dlouhá – ale na krátké vzdálenosti se klidně spolehněte na jednoduché props.