Accueil / Articles / Tri des mises à niveau de React 19 : Quelles nouvelles API remplacent les solutions de contournement que vous utilisez

Tri des mises à niveau de React 19 : Quelles nouvelles API remplacent les solutions de contournement que vous utilisez

Une analyse pratique de l’utilisation des Server Actions, de useOptimistic et du React Compiler, accompagnée des précautions importantes ainsi que d’un plan pour adopter en premier les modifications apportées par React 19.

1579 mots

React 19 a introduit plus de fonctionnalités API que toute autre version au cours des dernières années, et l’abondance d’informations disponibles rend difficile de déterminer ce qui est essentiel pour une base de code existante. Pour la plupart des équipes, quelques nouvelles fonctionnalités remplacent déjà des solutions de contournement en usage, une autre change la façon dont on aborde le chargement des données, et le reste peut attendre. Ci-dessous figurent celles qui sont importantes, avec du code avant et après modification ainsi que les précautions faciles à négliger, afin de pouvoir planifier la mise à niveau en fonction de son utilité réelle.

use : lecture des Promises et du Contexte pendant le rendu

L’API use est l’ajout le plus significatif, et son comportement diffère de celui de tous les autres hooks que l’on connaît. Les hooks classiques doivent être appelés au niveau le plus élevé d’un composant, dans le même ordre à chaque rendu. use échappe à cette règle : on peut l’appeler après un retour précoce, à l’intérieur d’une condition ou dans une boucle.

Dans l’exemple ci-dessous, le composant affiche une vue invité en l’absence de userId, et ce n’est qu’à ce moment-là qu’il lit les informations de l’utilisateur :

// React 19 — use() can be called inside conditionals and loops
function UserProfile({ userId }) {
  if (!userId) return <GuestView />;

  // This is valid in React 19
  const user = use(fetchUser(userId));

  return <div>{user.name}</div>;
}

La véritable puissance réside dans ce que use accepte : une Promise ou un Context. Lorsqu’une Promise est fournie, React suspend le composant jusqu’à ce qu’elle soit résolue. La comparaison ci-dessous montre à quel point les méthodes anciennes sont moins efficaces. La version ancienne gère les données et un indicateur de chargement dans l’état, effectue la requête via un effet et affiche manuellement un indicateur de rotation ; la version React 19 lit directement la valeur :

// Before React 19
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId).then(data => {
      setUser(data);
      setLoading(false);
    });
  }, [userId]);

  if (loading) return <Spinner />;
  return <div>{user.name}</div>;
}

// React 19
function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  return <div>{user.name}</div>;
}

Le travail n’a pas disparu, il a simplement été déplacé : la limite la plus proche de <Suspense> affiche le contenu de secours tant que la Promise est en attente, tandis que la limite d’erreur la plus proche gère les cas de rejet. Le composant ne décrit que le parcours de succès.

La Promise doit être stable

use attend la même Promise à chaque rendu. Si fetchUser(userId) en crée une nouvelle à chaque fois, React suspend à nouveau à chaque occasion, ce qui entraîne des requêtes répétées ou un composant qui ne se résout jamais ; React avertit en effet des Promises non mémorisées créées lors du rendu dans les composants clients. Considérez donc ces exemples comme des illustrations de la structure souhaitée. La Promise doit provenir d’un élément qui la mémorise : une bibliothèque de données consciente de Suspense, un chargeur de framework, ou un composant serveur qui lance la requête et transmet la Promise en tant que prop. Il est parfois suggéré d’encadrer l’appel dans useMemo, mais React ne garantit pas que les valeurs mémorisées soient conservées, ce qui en fait un cache peu fiable.

Les actions serveur en tant que fonctionnalité de React

Les utilisateurs du App Router de Next.js connaissent déjà les Server Actions. Dans React 19, elles font partie intégrante de React lui-même (les documents les appellent désormais Server Functions) et sont disponibles dans tout framework prenant en charge les Server Components.

L’exemple définit une fonction asynchrone marquée avec 'use server', qui insère un utilisateur et révérifie la liste, puis la transmet à la propriété action d’un formulaire :

// Server Action — runs on the server, called from the client
async function submitForm(formData) {
  'use server';

  const name = formData.get('name');
  await db.users.create({ name });
  revalidatePath('/users');
}

// Client component
function UserForm() {
  return (
    <form action={submitForm}>
      <input name="name" />
      <button type="submit">Add User</button>
    </form>
  );
}

La propriété action de <form> accepte désormais une fonction, y compris une asynchrone ; React l’exécute lors de la soumission et lui transmet les données FormData. L’état en attente, les mises à jour optimistes et les erreurs proviennent d’API associées : useFormStatus ou useActionState pour l’état en attente et les résultats, useOptimistic pour un retour instantané, ainsi que des limites d’erreur pour gérer les échecs. Vous écrivez la mutation ; React coordonne le reste.

Un détail dans cet extrait doit être ajusté dans une application réelle. Une fonction contenant la directive 'use server' en ligne ne peut être définie que dans un composant serveur. Si UserForm est un composant client, déplacez submitForm dans son propre fichier en plaçant 'use server' en haut, puis importez-le.

Tandis que les composants serveur éliminent l’interface utilisateur statique du paquet client, les actions serveurs suppriment les routes API manuellement écrites pour les mutations : moins de code, moins d’allers-retours. Traitez néanmoins chaque action comme un point de terminaison public et validez les entrées ainsi que l’autorisation à l’intérieur d’elle.

useOptimistic : retour instantané sans état dupliqué

Avant, afficher un résultat avant la confirmation du serveur signifiait gérer manuellement un état supplémentaire. useOptimistic prend l’état actuel ainsi qu’une fonction de mise à jour similaire à un réducteur, et retourne l’état à afficher ainsi qu’une fonction qui applique la modification optimiste. Ici, l’ajout d’une tâche insère un élément temporaire marqué comme en attente, qui s’affiche avec une légère transparence jusqu’à ce que l’enregistrement soit terminé :

function TodoList({ todos }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (currentTodos, newTodo) => [...currentTodos, newTodo]
  );

  async function handleAdd(text) {
    addOptimisticTodo({ id: 'temp', text, pending: true });
    await saveTodo(text); // Server Action
  }

  return (
    <ul>
      {optimisticTodos.map(todo => (
        <li key={todo.id} style={{ opacity: todo.pending ? 0.7 : 1 }}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

Lorsque vous appelez addOptimisticTodo, React affiche immédiatement la liste optimiste. Lorsque l’action associée est terminée, la valeur optimiste est abandonnée et React affiche à nouveau à partir de la propriété todos, qui devrait alors contenir l’élément enregistré.

Auparavant, vous conserviez des copies confirmées et en attente des données et les nettoyiez manuellement lorsqu’elles présentaient des divergences ; désormais, c’est le mécanisme de hook qui gère ce cycle de vie. Deux conditions sont importantes. Premièrement, la mise à jour optimiste doit avoir lieu au sein d’une action ou d’une transition, par exemple une fonction transmise à la propriété action d’un formulaire ou enveloppée dans startTransition ; l’appel depuis un simple gestionnaire d’événement provoque une alerte de React et la mise à jour pourrait ne pas s’afficher. Deuxièmement, le composant parent doit effectivement recevoir de nouvelles données todos après enregistrement, généralement grâce à une révalidation, sinon le nouvel élément disparaîtra lorsque l’état optimiste sera réinitialisé. Les identifiants temporaires tels que 'temp' entraînent également des conflits si deux éléments sont ajoutés rapidement, il convient donc d’en générer un unique. Nous abordons ces cas limites dans cinq modes d’échec liés à useOptimistic.

Le compilateur React : la mémorisation par défaut

Le compilateur React, anciennement React Forget, est apparu avec React 19 en tant qu’étape de compilation optionnelle, et il a l’impact le plus durable sur le code quotidien. Il insère la mémorisation au moment de la compilation, de sorte que les valeurs et les fonctions d’action sont réutilisées lorsque les entrées restent inchangées, et que les enfants évitent de se rérender lorsque leurs props sont identiques. Les utilisations manuelles de useMemo, useCallback et React.memo deviennent pour la plupart inutiles, comme le montre cette comparaison :

// Before: manual memoization required
const expensiveValue = useMemo(() => compute(a, b), [a, b]);
const stableCallback = useCallback(() => doSomething(id), [id]);
const MemoizedChild = React.memo(ChildComponent);

// After React Compiler: write normal code
const expensiveValue = compute(a, b);
const handleClick = () => doSomething(id);
// ChildComponent renders only when its props change - automatically

Il reste nécessaire de comprendre quand et pourquoi les composants se rérenderont, mais les solutions manuelles sont bien moins importantes. Notre aperçu des modèles qui provoquent des rérenders inutiles reste un document de référence utile.

Pour les nouveaux projets, le compilateur constitue un choix par défaut fiable. Dans du code existant, implémentez-le avec prudence : il suppose que les composants sont purs et respectent les règles de React, de sorte que du code qui modifie ses données pendant le rendu peut se comporter différemment une fois compilé. Activez-le progressivement, exécutez vos tests et utilisez le plugin ESLint pour identifier d’abord les violations. Consultez la documentation actuelle de React pour connaître son statut de publication et les versions de React prises en charge.

Quoi adopter, et dans quel ordre

Bénéfices immédiats, faible risque

  • useOptimistic là où les formulaires interagissent avec l’état du serveur.
  • Server Actions si vous utilisez Next.js 14 ou une version ultérieure et souhaitez remplacer les routes API pour les mutations.

Définitif à planifier

  • use une fois que votre couche de données génère des Promises stables et mémorisées ; il s’intègre naturellement avec Suspense.
  • L’utilisation du compilateur React dans de nouveaux projets, ou d’abord dans les parties bien testées d’une application existante.
  • Pas urgent pour la plupart des applications

    • Les modifications concernant les refs : ref peut désormais être transmis en tant que propriété ordinaire aux composants fonctionnels, ce qui rend forwardRef inutile.
    • Des améliorations au contexte, qui consistent principalement en des changements liés à l’expérience utilisateur plutôt qu’en de nouveaux comportements.
    • Le support des métadonnées de documentation, qui permet aux composants d’afficher les balises <title> et <meta> que React insère dans la partie en-tête du document.

    Points clés

    • React 19 représente une évolution plutôt qu’une réécriture radicale. Ses nouvelles API formalisent des schémas que les équipes de production utilisent déjà manuellement : mises à jour optimistes, mutations serveur et lecture conditionnelle des données.
    • use déplace le chargement et la gestion des erreurs vers Suspense et les limites d’erreur, mais ne fonctionne bien que avec des Promises mémorisées en dehors du rendu.
    • Server Actions et useOptimistic s’associent naturellement : le premier gère l’écriture, tandis que le second masque sa latence.
    • Le compilateur rend la mémorisation par défaut, à condition que vos composants respectent les règles de React.

    La question pertinente n’est pas de savoir s’il faut mettre à jour, mais quel de ces outils remplace une solution temporaire que vous utilisez actuellement. Partez de là.

    Lectures complémentaires

  • Formes asynchrones dans React 19 avec use, useActionState et useOptimistic — Comment use(), useActionState, useFormStatus et useOptimistic remplacent les mécanismes manuels de chargement et les indicateurs d’erreur dans React 19, ainsi que les pièges que chacun de ces hooks cache discrètement.