Learning
Všechny články
Frontend7. září 2026 6 min

Stav visí na pozici ve stromu, ne na komponentě

React si pamatuje místo v render tree, tvoje podmínky nevidí

Stav nepatří komponentě, patří místu, kde se vykreslila

Stav se intuitivně chápe jako něco, co má komponenta v sobě. Nemá. Stav drží React a váže si ho na pozici v render tree, tedy ve stromu, který z tvého renderu skutečně vznikl.

Z toho plyne pravidlo, ze kterého vychází všechno ostatní. Když na stejné pozici vykreslíš stejný typ komponenty jako minule, React její stav zachová. Změní se typ, stav zmizí i s celým podstromem. Zmizí komponenta, zmizí stav.

{isFancy ? <Counter isFancy={true} /> : <Counter isFancy={false} />}

Obě větve jsou v JSX jinde, ale ve stromu jsou na stejné pozici a jde o stejný typ. Počítadlo přepnutí přežije, i když se mu vymění všechny props.


React nevidí tvůj kód, vidí výsledek

Tohle je místo, kde intuice selhává nejčastěji. Pozice není místo v souboru, je to místo ve stromu.

if (isFancy) {
  return <div><Counter isFancy={true} /><label>...</label></div>;
}
return <div><Counter isFancy={false} /><label>...</label></div>;

Dva samostatné return, dvě různé větve podmínky, a stav se přesto zachová. Obě cesty vyrobí <Counter /> jako první dítě <div>. React tu podmínku nikdy neviděl, dostal jen hotový strom, a v obou případech je na první pozici stejný typ.

Stejným mechanismem funguje mazání. {showB && <Counter />} po přepnutí na false komponentu ze stromu odebere a stav je nenávratně pryč. Když se vrátí, začíná od nuly.


key je identita, ne jen věc pro seznamy

key se učí u polí, ale to je jen jeho nejčastější použití. Ve skutečnosti je to jediný způsob, jak Reactu ručně říct, že dvě věci na stejné pozici nejsou totéž.

<Chat key={to.id} contact={to} />

Bez klíče zůstane rozepsaná zpráva ve formuláři i po přepnutí příjemce, protože <Chat /> je pořád stejný typ na stejné pozici. S klíčem React při změně to.id vidí jinou identitu, starou instanci zahodí a novou postaví od začátku. Celá oprava je jedna prop.

Funguje to i opačně: dvě komponenty rozhozené do různých větví lze klíčem prohlásit za různé, i když se střídají na stejném místě. A klíč stačí unikátní v rámci jednoho rodiče, globálně unikátní být nemusí.


Pravidlo o vnořených komponentách je jen důsledek

Zákaz definovat komponentu uvnitř jiné komponenty se často předává jako stylová konvence. Není. Je to přímý důsledek pravidla o pozici.

export default function MyComponent() {
  function MyTextField() {
    const [text, setText] = useState('');
    return <input value={text} onChange={e => setText(e.target.value)} />;
  }
  return <MyTextField />;
}

Každý render rodiče vyrobí novou funkci MyTextField. Nová funkce je z pohledu Reactu jiný typ na stejné pozici, takže se stará instance zahodí a stav se resetuje. Prakticky to znamená, že se input maže při každém stisku klávesy. Přesunutí definice na úroveň modulu problém odstraní, protože identita funkce přestane kolísat.


Co si z toho odnést

Když ladíš stav, který se drží, i když se držet nemá, neptej se, jestli je to stejná komponenta. Ptej se, jestli je to stejné místo. Ta otázka odpoví na formulář, který si pamatuje předchozího uživatele, i na modál, který si nese hodnoty z minulého otevření.

Reset stavu není potřeba psát ručně přes useEffect, který při změně props všechno vynuluje. To je obcházení mechanismu, který už existuje. Správný nástroj je key a jeho hodnotou je to, co definuje identitu — ID příjemce, ID záznamu, ID kroku průvodce.

A obráceně: když se stav ztrácí a nemá, hledej něco, co při renderu mění identitu typu. Komponenta definovaná uvnitř jiné je nejčastější případ, ale stejně se chová i typ vytvořený voláním nebo skládáním za běhu.

Zdroje

Poslech místo čtení — 1 zdroj k načtení.