Logo

Hooky & Kontrola stavu

V minulé kapitole jste si možná v jednom z příkladů všimli „magické“ funkce useState, díky které se po kliknutí na tlačítko změnilo číslo na tlačítku.
V této kapitole se podíváme na to, jak můžeme ovládat stav naší aplikace a jaké všechny hooky máme k dispozici.

import { useState } from 'react';

function Tlacitko() {
  const [pocet, nastavitPocet] = useState(0);
  return (
      <button onClick={() => nastavitPocet(pocet + 1)}>
        Zmáčknuto {pocet} krát.
      </button>
  );
}

Jedná se o nejprimitivnější hook – funkci, která nám vrátí tzv. getter a setter, neboli hodnotu a funkci pro její změnu.

Proč ale musíme používat useState a nemůžeme proměnnou jen nastavit?

import { useState } from 'react';
function RozbiteTlacitko() {
// ❌ Špatně – nic se nezmění
let pocet = 0;
return (
    <button onClick={() => pocet++}>
      Rozbité tlačítko zmáčknuto {pocet} krát.
    </button>
);
}
function Tlacitko() {
// ✅ Dobrá praktika
const [pocet, nastavitPocet] = useState(0);
return (
    <button onClick={() => nastavitPocet(pocet + 1)}>
      Funkční tlačítko zmáčknuto {pocet} krát.
    </button>
);
}

export default function App() {
return (
<div>  
  <RozbiteTlacitko />
  <Tlacitko />
</div>
);
}

Nejjednodušší odpověď je, že React nemá jak poznat, že se hodnota změnila – tudíž netuší, že má komponentu znovu vykreslit.

Další často používaný hook je useEffect. Na první pohled jednoduchý, ale může způsobit spoustu problémů, takže s ním zacházejte opatrně.
useEffect je hook, který bere dva parametry. Prvním je funkce, která se má zavolat při změně. Druhým je pole závislostí – hodnot, při jejichž změně se funkce spustí.

useEffect můžeme spustit při inicializaci komponenty i při jejím opuštění. (O tom více v další kapitole.)

import { useState, useEffect } from "react"

export default function App() {
const [pocet, nastavPocet] = useState(5)

// Funkce se zavolá při změně hodnoty 'pocet'
useEffect(()=>{
	console.log('Počet změněn')
},[pocet])

// Funkce se zavolá při inicializaci aplikace
useEffect(()=>{
	console.log('Inicializace komponenty')
  return () => {
    // Toto se může hodit pro odstranění event listenerů
    console.log('Opuštění komponenty')
  }
},[])

return (
	<div className='button-component'>
		<button onClick={() => nastavPocet(pocet+1)}>Tlačítko zmáčknuto {pocet} krát</button>
	</div>
)
}

Zde vidíme realističtější příklad použití hooku useEffect.

import { useEffect } from 'react';
import { pripojSeKchatu } from './chat.js';

function ChatSkupina({ idSkupiny }) {
  const adresaServeru = 'wss://chat.com';
  const [zpravy, nastavZpravy] = useState('');

  useEffect(() => {
    // Při inicializaci komponenty se spojí klient se serverem
  	const spojeni = pripojSeKchatu(adresaServeru, idSkupiny);
    spojeni.connect();
    nastavZpravy(spojeni.zpravy);

  	return () => {
      // Nechceme nechávat spojení běžet zbytečně – po opuštění stránky ho ukončíme
      spojeni.disconnect();
  	};
  }, [adresaServeru, idSkupiny]);
}

Hooky useMemo a useCallback zmiňuji spíše proto, že se s nimi určitě setkáte v pokročilejších aplikacích. Slouží hlavně k optimalizaci.
Na začátku tohoto roku byl ale představen React Compiler, který by měl tyto optimalizace zvládat automaticky – proto je zde probereme jen stručně.

V obou případech jde o funkce se dvěma parametry. Prvním je funkce, která něco vrací (u useMemo hodnotu, u useCallback funkci). Druhým je pole závislostí. Pokud se nějaká hodnota změní, výpočet proběhne znovu – jinak se použije ta původní.

import { useMemo, useCallback, useState } from 'react';

function App() {
  const [jmena, nastavJmena] = useState(["Adéla", "Bob", "Cindy", "Arnošt", "Dan", "Adam"]);

  // Třídění pole může být náročná operace, proto je výhodné výsledek zapamatovat
  const jmenaPodleAbecedy = useMemo(() => jmena.slice().sort().join(", "), [jmena]);
  
  const vypisJmenaAObratJe = useCallback(() => {
    console.log("Původní:", jmena.join(", "));
    nastavJmena([...jmena].reverse());
  }, [jmena]);

  return (
    <div>
      <button onClick={vypisJmenaAObratJe}>Obrať pořadí jmen</button>
      <div>Jména podle abecedy: {jmenaPodleAbecedy}</div>
    </div>
  );
}

Poslední hook, který si v této kapitole vysvětlíme, je useContext. Umožňuje nám vytvořit tzv. kontext – neboli data, která jsou přístupná více komponentám (konkrétně všem „potomkům“ poskytovatele kontextu). Nejlépe to pochopíme na příkladu:

import { createContext, useContext, useState } from 'react';
const KontextTematu = createContext('světlé');

export default function Aplikace() {
const [tema, nastavTema] = useState('světlé');
return (
  <>
    {/* Poskytujeme aktuální téma všem podřízeným komponentám */}
    <KontextTematu.Provider value={tema}>
      <Panel nazev="Vítejte!">
        <Tlacitko onClick={() => {
          // Přepínání mezi světlým a tmavým tématem
          nastavTema(tema === 'tmavé' ? 'světlé' : 'tmavé');
        }}>
          Přepnout téma
        </Tlacitko>
      </Panel>
    </KontextTematu.Provider>
  </>
);
}

function Panel({ nazev, children }) {
const tema = useContext(KontextTematu);
return (
  <section style={{ backgroundColor: tema === "světlé" ? "#fff" : "#000" }}>
    <h1 style={{ color: tema === "světlé" ? "#000" : "#fff" }}>{nazev}</h1>
    {children}
  </section>
);
}

function Tlacitko({ children, onClick }) {
const tema = useContext(KontextTematu);
return (
  <button onClick={onClick}>
    {children} na {tema === "světlé" ? "tmavé" : "světlé"} téma
  </button>
);
}

Toto však ale nejsou všechny hooky, existuje i spousta dalších, zde jsme pouze zmínili ty nejčastější.

Pokud by jste narazili na nějaký který případně neznáte, neváhejte se podívat do dokumentace

Malý tip nakonec: hook poznáte podle toho že většínou začínají na use.