React 19 nouveautés et fonctionnalités

  React

La version 19 de React introduit plusieurs améliorations significatives qui visent à simplifier le développement et améliorer les performances des applications.

A l’heure ou cet article a été écrit, React 19 est encore en version Canary. La version officielle devrait être publiée durant l’année 2024.

Voici les points clés de la version 19 :

React Compiler

Le nouveau compilateur de React optimisera automatiquement les rendus sans nécessiter l’utilisation des hooks useMemo() ou useCallback(). De même pour la fonction memo, qui permet d’optimiser les re-renders en fonction des props d’un composant. Ceci réduit le besoin d’optimisations manuelles et simplifie le code.

Actuellement, Instagram utilise déjà cette nouvelle fonctionnalité.

// Avant
const isInputEmpty = useMemo(() => inputValue.trim() === "", [inputValue]);

// Après React 19, le useMemo sera ajouté automatiquement par le compilateur si nécessaire
const isInputEmpty = inputValue.trim() === "";

React Server Components

Les Server Components permettront d’exécuter du code côté serveur pour améliorer le SEO et les performances initiales des applications.

Next JS utilise déjà les React Server Components depuis la version 13.4, avec le app router.

Avec React 19, les Server Components seront directement intégrer dans React.

Par défaut, avec React tous les composants seront exécutés du côté client. A l’inverse avec Next JS et le app router, les composants par défaut sont tous considérés Server Components.

Si vous spécifiez la directive use server, le composant sera alors exécuté côté serveur.

Exemple :

"use server";

import marked from "marked";
import sanitizeHtml from "sanitize-html";

async function BlogPage({ page }) {
  const content = await file.readFile(`${page}.md`);

  return <div>{sanitizeHtml(marked(content))}</div>;
}

Actions

Les actions simplifient la gestion des formulaires en permettant d’utiliser l’attribut action de la balise form, pour gérer la soumission des formulaires du côté client ou du côté serveur.

Voici la définition d’une action exéctuée côté serveur :

"use server";

export const submitData = async (userData) => {
  const newUser = {
    username: userData.get("username"),
    email: userData.get("email"),
  };
  console.log(newUser);
};

Voici le composant client Form qui utilise cette action :

import { submitData } from "./lib";

const Form = () => {
  return (
    <form action={submitData}>
      <div>
        <label>Name</label>
        <input type="text" name="username" />
      </div>
      <div>
        <label>Name</label>
        <input type="text" name="email" />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};

export default Form;

Web Components

React 19 facilitera l’intégration de Web Components dans les applications React, permettant une meilleure réutilisation du code et intégration avec d’autres bibliothèques.

Métadonnées du Document

React 19 introduira des balises natives comme <title> et <meta> pour mieux gérer les métadonnées HTML depuis vos composants React.

Auparavant il était nécessaire de manipuler le DOM directement pour mettre à jour les attributs sur les balises meta et title.

Exemple :

const Page = () => (
  <>
    <title>Titre de la Page</title>
    <meta name="description" content="Description de la Page" />
  </>
);

Chargement d’Assets

La nouvelle fonctionnalité de chargement d’assets permettra de charger les ressources en arrière-plan, améliorant le temps de chargement perçu par les utilisateurs.

forwardRef

Avec React 19, la fonction forwardRef devient en quelquesorte obsolète, car la propriété ref peut désormais être passée directement dans les props des composants.

Pour rappel forwardRef est une fonction qui permet de transmettre une ref vers un composant. Le composant parent peut ainsi manipuler le noeud DOM du composant enfant.

Cela permettra de simplifier le code de vos composants.

Exemple :

import React from "react";

const ExampleButton = ({ ref, children }) => (
  <button ref={ref}>{children}</button>
);

Nouveaux Hooks

De nouveaux hooks sont introduits pour améliorer la gestion des états et interactions asynchrones.

use

Ce nouveau hook permet de faciliter la gestion des promesses et des contextes React.

Il va de pair avec le composant Suspense qui permet d’attendre le chargement du composant qui utilise le hook use.

Exemple :

import { use } from "react";

const fetchUsers = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  return res.json();
};

const UsersItems = () => {
  const users = use(fetchUsers());

  return (
    <ul>
      {users.map((user) => (
        <div key={user.id}>
          <h2>{user.name}</h2>
          <p>{user.email}</p>
        </div>
      ))}
    </ul>
  );
};
export default UsersItems;

Il sera également possible d’utiliser use pour utiliser un contexte React :

Exemple :

const Card = () => {
  const { theme, toggleTheme } = use(ThemeContext);

  return (
    <div>
      <h1>{theme} Theme Card</h1>
      <button onClick={toggleTheme}>
        {theme === "light" ? "Switch to Dark Mode" : "Switch to Light Mode"}
      </button>
    </div>
  );
};

useFormStatus

Ce hook permet de suivre le statut de la soumission d’un formulaire. Il peut être utilisé avec action et il vous donnera les informations contenus depuis la dernière soumission du formulaire.

Ce hook permet notamment d’afficher un message ou un indicateur de chargement, en cas de soumission en cours du formulaire.

Exemple :

import { useFormStatus } from "react-dom";

function SubmitButton() {
  const status = useFormStatus();

  // la propriété pending permet de savoir si le formulaire est en cours de soumission
  return (
    <button disabled={status.pending}>
      {status.pending ? "Submitting..." : "Submit"}
    </button>
  );
}

// permet de simuler une promesse de 3 secondes
const formAction = async () => {
  await new Promise((resolve) => setTimeout(resolve, 3000));
};

const FormStatus = () => {
  return (
    <form action={formAction}>
      <SubmitButton />
    </form>
  );
};

export default FormStatus;

useFormState

Ce hook permet de gérer un état lors de la soumission d’un formulaire. Il accepte en premier paramètre une fonction qui peut être une action asynchrone ou une server action.Le deuxième argument est l’état initial avant la soumission.

Exemple :

import { useFormState } from "react-dom";

function submitForm(prevState, queryData) {
    const name = queryData.get("username");

    console.log(prevState); // état précédent du formulaire

    if (name === "john") {
      return {
        success: true,
        text: "Welcome",
      };
    } else {
      return {
        success: false,
        text: "Error",
      };
    }
  };

const FormState = () => {
  const [message, formAction] = useFormState(submitForm, undefined);

  return (
    <form action={formAction}>
      <label>Name</label>
      <input type="text" name="username" />
      <button>Submit</button>
      {message && <h1>{message.text}</h1>}
    </form>
  );
};

export default FormState;

useOptimistic

Ce hook permet d’afficher un état différent pendant qu’une action asynchrone est en cours.

Il permettra d’améliorer l’expérience utilisateur et d’obtenir des réponses instantanées. Si une erreur se produit, et que l’état dont il dépend diffère une réconciliation sera automatiquement effectuée, impliquant un re-rendu.

Par exemple, lorsqu’une action est en cours, nous pouvons afficher un « état » pour donner à l’utilisateur une réponse immédiate. Une fois la réponse réelle renvoyée par le serveur, l’état « optimiste » sera mis à jour en conséquence.

Exemple :

import { useOptimistic, useState } from "react";

const Optimistic = () => {
  // état de base  
  const [messages, setMessages] = useState([
    { text: "initial state", sending: false, key: 1 },
  ]);
  // état optimiste qui s'appuie sur l'état de base
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => [
      ...state,
      {
        text: newMessage,
        sending: true,
      },
    ]
  );

  async function sendFormData(formData) {
    const sentMessage = await fakeDelayAction(formData.get("message"));
    // Remplace l'état optimiste par le nouveau message
    setMessages((messages) => [...messages, { text: sentMessage }]);
  }

  // permet de simuler une promesse de 1 seconde
  async function fakeDelayAction(message) {
    await new Promise((res) => setTimeout(res, 1000));
    return message;
  }

  const submitData = async (userData) => {
    // met à jour l'état optimise instantanément
    addOptimisticMessage(userData.get("username"));

    await sendFormData(userData);
  };

  return (
    <>
      {optimisticMessages.map((message, index) => (
        <div key={index}>
          {message.text}
          {!!message.sending && <small> (Sending...)</small>}
        </div>
      ))}
      <form action={submitData}>
        <h1>OptimisticState Hook</h1>
        <div>
          <label>Username</label>
          <input type="text" name="username" />
        </div>
        <button type="submit">Submit</button>
      </form>
    </>
  );
};

export default Optimistic;