< Zpět na články

React 19 FTW

Jakub BaierlJakub Baierl
01. července 2024

Nepamatuju si větší napětí v React komunitě od releasu react hooků ve verzi React 16.8. Konečně se to po pár letech povedlo, a to konkrétně s verzí React 19. Je boom opodstatněný, nebo zas jen javascriptová bublina spustila davové šílenství? Kdo jsem já, abych soudil, verdikt nechám na vás. 👀

Než se pustíte do nespočtu videí a blog postů, krátce bych upozornil, že v různých zdrojích se vyskytují chybné příklady a použití. Nebudu na nikoho ukazovat prstem – API nových featur se měnilo za pochodu, a tudíž se před oficiálním releasem protagonisti daných materiálů nemuseli dostat k relevantním informacím. Buďte obezřetní a hledejte materiály vydané až PO oficiálním releasu beta verze, kterým je 25. 4. 2024. 🏷️

Většinu nových featur jsem testoval na konkrétních příkladech, které můžete i sami vyzkoušet v tomto repozitáři, který mi sloužil jako playground a nechávám ho dále k dispozici i pro vás. Pár hlavních bodů si ukážeme přímo v článku, zbytek už dohledáte v ukázkovém projektu.

Pokud byste chtěli sami upgradnout na novou verzi, doporučuju vám projít si nový migration guide, který jednak obsahuje přesný návod na instalaci a jednak i použití připravených codemode skriptů na automatickou migraci vašeho stávajícího kódu, což velmi oceňuju.

🦹 Jaké key features nová verze Reactu obsahuje?

Actions (nebo dříve Server actions) nám umožňují z klienta volat asynchronní funkce, které jsou vykonány na serveru. Pomocí direktivy "use server" použitý framework vytvoří referenci a po zavolání z klient komponenty pošle request na server, kde funkci vykoná. Typický příklad zavolání takové funkce je změna jména uživatele, kde jednoduchý formulář může vypadat takto:

export const Form = () => {
  return (
    <form action={updateName}>
      <input type="text" name="name" />
      <button type="submit">{"Upravit jméno"}</button>
    </form>
  );
};

Jelikož < form > v ukázce je přímo (jak v týmu pracovně říkáme) “rejpnutá” nativní komponenta do reactí komponenty, po submitu formuláře je přímo zavolána akce, která nám upraví nové jméno v databázi (což máme nasimulované jen jednoduchou Promise).

"use server";

export async function updateName(name?: string) {
  if (!name) {
    return { error: "Jméno je povinné" };
  }

  // Todo: Update name via API
  await new Promise((r) => setTimeout(r, 3000));
  // await db.users.updateName(name);
}

✨ Spousta zábavy s novými React hooky

Zůstaňme u stejného příkladu a zkusme ho rozšířit o submit tlačítko, které se bude měnit podle stavu formuláře.

<form action={updateName}>
  <input type="text" name="name" />
  <SubmitButton />
</form>

Běžný příklad ze života je po odpálení formuláře změnit submit tlačítko na stav disabled (popřípadě mu ještě změnit text), aby uživatel věděl, že se něco děje a zároveň nemohl daný formulář odeslat vícekrát za sebou. Na to nám poslouží useFormStatus, který sám vyhodnotí, v kontextu jakého formuláře se nachází, a vrátí nám stav daného formuláře.

export const SubmitButton = () => {
  const { pending } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? "Upravuju..." : "Upravit jméno"}
          </button>
  );
};

🚀 Jedeme dál…

Optimistické updaty asi všichni známe (pokud ne, příklad zde). Ve zkratce – místo abychom čekali na výsledek API requestu, uživateli zobrazíme nová data ihned a po dokončení daného API requestu data ponecháme nebo stav v opačném případě revertujeme. React 19 přišel se speciálním hookem jen pro tento use case, který se ale dá využít různými způsoby. V přiloženém repozitáři si můžete zkusit příklad live chatu (< OptimisticChat / >), kde se zprávy zobrazují ihned po odeslání a až po dokončení requestu se u nich zobrazují informace o jejich stavu na API.

React 19 FTW

Použití je velmi snadné, funkci po submitu formuláře (v tomto případě formAction), rozšíříme o metodu vytvořenou pomocí useOptimistic hooku, a tudíž lokální stav zpráv aktualizujeme ihned po odeslání zprávy (s tím, že každé zprávě přidáme flag sending: true) a pak teprve zprávu pošleme requestem na API.

async function formAction(formData: FormData) {
  addOptimisticMessage(formData.get("message"));
  formRef?.current?.reset();
  await sendMessage(formData);
}


const [optimisticMessages, addOptimisticMessage] = useOptimistic(
  messages,
  (state, newMessage) => [
    ...state,
    {
      text: newMessage,
      sending: true,
    },
  ]
);

Jakmile pak dostaneme z API výsledek, lokální stav zpráv přepíšeme skutečnými hodnotami, sending flag nastavíme na false a případně přidáme chybovou hlášku, pokud se zprávu na API nepovedlo uložit.

const [messages, setMessages] = useState([
  { text: "First message!", sending: false, key: 1, error: null },
]);


async function sendMessage(formData: FormData) {
  const sentMessage = await deliverMessage(formData.get("message"));
  setMessages((messages: Message[]) => [
    ...messages,
    {
      text: sentMessage.message ?? formData.get("message"),
      error: sentMessage.error,
    },
  ]);
}

Boží. 😍

😌 forwardRef? No more…

Není to velký, ale potěší to snad každýho. S novou verzí Reactu konečně můžeme posílat ref as prop do child komponent stejně jako posíláme jakékoliv jiné properties.

export const Form = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  return (
    <form>
      <InputWithRef ref={inputRef} />
      <SubmitButton />
    </form>
  );
};

export const InputWithRef = ({ ref, ...rest }: InputRefProps) => {
  return (
    <>
      <label>Jméno</label>
      <input ref={ref} type="text" name="name" {...rest} />
    </> 
  );
};

🎉🎉🎉

🧾Podpora pro Document metadata

Konečně se můžeme zbavit react-helmet či obdobných workaround knihoven pro nastavování metadat, jelikož nově můžeme metadata nastavovat přímo z komponent napříč celou appkou, a to klidně i v lazyloadovaných komponentách. Například pro stránky s dynamickým obsahem není problém nastavit metadata až po stažení příslušných dat pomocí jednoduchého přidání < title >{data.name}< /title > do vaší komponenty.

const UserDataComponent = () => {
  const data = useData();

  return (
    <>
      <h1>{data.name}</h1>
      <title>{data.name}</title>
    </>
  );
};

Stejně tak můžete lazyloadovat i stylesheety a další assety. 🏋️

💡A co Server components?

Ano, v React 19 budou server komponenty stable! Samozřejmě závisí na tom, jaký použijete framework a jak server komponenty chcete využívat. Dejte pozor na to, že k použití neslouží dříve zmíněná direktiva use-server (ta slouží jen pro Actions), ale vždy potřebujete nějaký server, kde server komponenty budou zpracovány, ať už po vyvolání requestu z klientu anebo už při buildu.

Máme ale možnost přiblížit se podobnému výsledku i v klientských komponentách, kde pomocí nového hooku use() můžeme docílit podmíněného vyrenderování konkrétních asynchronních komponent.

Dejme tomu, že chceme asynchronně stáhnout data z API a danou komponentu chceme zobrazit až tehdy, kdy data máme připravena.

const fetchData = async () => {
  "use server";

  const res = await fetch("https://api.chucknorris.io/jokes/random");
  return res.json();
};

export const PromiseUseExample = () => {
  const requestPromise = fetchData();
  // Note: Ideally use with useMemo to cache the promise
  // const requestPromise = useMemo(fetchData, []);

  return <SuspensedComponent promise={requestPromise} />;
};

Tento hook ve spolupráci se Suspense API nám zajistí, že se komponenta opravdu vyrenderuje až tehdy, kdy data budou dostupná, a ne jako dříve, kdy se komponenta vyrenderovala s prázdnými daty a pak znovu, když se stav dat změnil a developer musel řešit, kdy jaký kus komponenty zobrazovat a kdy ne.

const SuspensedComponent = ({ promise }: SuspensedComponentProps) => {
  const res = use(promise);

  return <p>{res.value}</p>;
};

const SuspensedComponentWrapper = ({ promise }: SuspensedComponentProps) => (
  <Suspense fallback={<p>čekám na data...</p>}>
    <SuspensedComponent promise={promise} />
  </Suspense>
);

Zároveň use() hook je jediný hook, který můžete volat podmíněně, například:

if (something) {
  const res = use(promise);
}

A to se vyplatí! 💰

📒 React Context a use() hook?

Od nové verze Context přijde s pozměněným API pojmenovaným Context as a Provider, čímž se nám trochu ztenčí kód, jelikož definovaný context, jak už název napovídá, můžeme renderovat přímo jako provider (no more < Context.Provider >, což je ale samozřejmě dál zpětně kompatibilní zápis).

const TestContext = createContext({});

export const ContextExample = () => {
  return (
    <TestContext value={{ name: "Nejlepší react verze" }}>
      <ContextComponent />
    </TestContext>
  );
};

A zároveň s použitím nového use() hooku můžeme ke kontextu přistoupit v jakékoliv child komponentě v každé úrovni vnoření.

const ContextComponent = () => {
  const res = use(TestContext);

  return <p>Context data: {res.name}</p>;
};

Další nové featury, vylepšené (nejen hydration) errory a další příklady užití už jsou dostupné na oficiálním webu.

🤖 Nezapomínejme na React Compiler!

React Compiler sice není součástí React 19, avšak jeho použití je touto verzí podmíněno. Proto mi přijde vhodné ho tu krátce představit, ať víme, na co se můžeme těšit.

Pamatujete si a používáte před pár lety představené hooky jako useMemo, useCallback nebo samotné memo?

….klidně na ně můžete zapomenout, protože React Compiler slibuje, že to vyřeší za vás!

Automatická memoizace je game changer, který nám nejen zlepší developer experience, ale i zrychlí naše appky až o slíbených 20 %. Podmínkou správné funkcionality compileru je dodržování sady pravidel známých jako Rules of react. Zároveň nový compiler používá i svůj vlastní eslint plugin, který si musíte doinstalovat.

Velmi kladně hodnotím možnost použití compileru jen na část codebase pomocí konfiguračního souboru:

const ReactCompilerConfig = {
  sources: (filename) => {
    return filename.indexOf('src/path/to/dir') !== -1;
  },
};

anebo použítím direktivy “use memo” tam, kam chcete compiler pustit.

Pro více informací o instalaci a použití navštivte oficiální web.

🏆 Shrnutí

Nová verze obsahuje spoustu užitečných features, které jsou velmi jednoduše a rychle adaptovatelné. Jak je u Reactu zvykem, ani tady nás nečeká žádná breaking change, která by porušila zpětnou kompatibilitu.

V přiloženém repozitáři najdete všechny příklady popsané výše a ještě nějaké další, které už se do blogu nevešly.

Pro odlehčení na závěr jsem připravil jednoduchý tier list, který slouží jako bodový žebříček daných funkcionalit. Můžete se inspirovat mým hodnocením, nebo si udělat i to své zde.

Unnamed (2).png

Pokud byste měli zájem o více informací i s komentářem, koukněte na video z meetupu nebo prezentaci.

🗣️ Zdroje

React Compiler: In-Depth Beyond React Conf 2024 They made React great again? React Compiler React 19 - Dev Blog React 19 Is FINALLY Here New Hooks Explained React 19 - New features and updates Server components

Jakub Baierl
Jakub Baierl
Frontend Team LeadKdyž Jakub zrovna nebaví okolí svými vtípky, žije hudbou (ať už na pódiu, nebo před ním) a šije trička. V Ackee patří k mazákům, takže kromě historek oplývá tunou zkušeností.

Máte zájem o spolupráci? Pojďme to probrat osobně!

Napište nám >