Développement Web

React : construire des interfaces modernes

7 دقائق للقراءة

React 19.2 (octobre 2025) marque le moment où la bibliothèque bascule définitivement vers un modèle full-stack : Server Components stables, Server Actions intégrées, hook useActionState pour les formulaires, hook use() pour les promesses, Partial Pre-rendering pour servir le statique depuis CDN. Le React Compiler optimise désormais automatiquement les memoizations, ce qui rend la majorité des useMemo et useCallback obsolètes. Ce tutoriel construit pas à pas une application React 19 moderne avec Vite, depuis le premier composant jusqu’aux Server Actions, en gardant la rigueur des bonnes pratiques 2026.

Prérequis

  • Node.js 22 LTS
  • pnpm ou npm
  • Un éditeur supportant TypeScript (VS Code recommandé)
  • Notions de JavaScript ES2022 et HTML/CSS
  • Niveau attendu : débutant à intermédiaire
  • Temps estimé : 90 minutes

Étape 1 — Comprendre ce que React 19 change

Avant 19, React était une bibliothèque côté navigateur. Depuis 19, c’est un modèle de composants qui s’étend du serveur au client. Les Server Components exécutent leur fonction côté serveur uniquement, peuvent appeler la base de données, et envoient au navigateur le HTML rendu plus une description sérialisée pour l’hydratation. Les Client Components (préfixe "use client") restent côté navigateur pour l’interactivité. Cette dualité est devenue le standard via Next.js 15, qui traite chaque fichier dans app/ comme un Server Component par défaut.

Aspect Avant React 19 React 19+
Memoization useMemo / useCallback manuels React Compiler automatique
Données async useEffect + setState use(promise) + Suspense
Formulaires useState + onSubmit useActionState
Server-side SSR (rendu HTML) RSC (composants serveur)
SEO + perf Hydratation complète Partial Pre-rendering

Pour démarrer, on n’a pas besoin d’aller tout de suite vers les Server Components. Une SPA classique avec Vite + React 19 reste la voie la plus rapide pour apprendre, et permet d’adopter ensuite Next.js si la SEO ou la fetching côté serveur deviennent nécessaires.

Étape 2 — Créer un projet React avec Vite

Vite est l’outil de build privilégié en 2026 pour démarrer un projet React. Le scaffolding officiel inclut TypeScript, le hot reload sub-100 ms et un bundle de production optimisé sans configuration.

pnpm create vite@latest monapp -- --template react-ts
cd monapp
pnpm install
pnpm dev          # http://localhost:5173

Le serveur démarre en moins d’une seconde. La structure générée comprend src/main.tsx (point d’entrée), src/App.tsx (composant racine), vite.config.ts. Le fast refresh conserve l’état des composants à chaque sauvegarde — gain de productivité majeur sur les longs développements de formulaires ou de wizards.

Étape 3 — Premier composant fonctionnel

Un composant React est une fonction qui retourne du JSX. Les hooks ajoutent l’état et les effets. Le typage TypeScript est inline et n’exige aucune annotation redondante.

// src/components/Compteur.tsx
import { useState } from "react";

type Props = { initial?: number };

export function Compteur({ initial = 0 }: Props) {
  const [count, setCount] = useState(initial);

  return (
    <div className="compteur">
      <p>Compteur : {count}</p>
      <button onClick={() => setCount(c => c + 1)}>
        Incrémenter
      </button>
    </div>
  );
}

Trois points à observer. Le useState retourne un tuple valeur, setter. Le setter accepte une fonction (c) => c + 1 pour les mises à jour basées sur la valeur précédente — pattern obligatoire dès qu’on fait plusieurs setCount dans la même handler. Les props ont une valeur par défaut au niveau de la déstructuration plutôt qu’avec defaultProps qui est désormais déprécié. Avec le React Compiler, plus besoin d’envelopper le composant dans memo() : la mémoization est automatique.

Étape 4 — Récupérer des données avec use() et Suspense

Avant 19, on faisait fetch dans un useEffect avec un useState pour stocker le résultat. Cette pratique est désormais déconseillée : le hook use() + Suspense donne un code plus simple et plus performant.

// src/components/Articles.tsx
import { use, Suspense } from "react";

type Article = { id: number; title: string; body: string };

function fetchArticles(): Promise<Article[]> {
  return fetch("/api/articles").then(r => r.json());
}
const articlesPromise = fetchArticles();

function ListeArticles() {
  const articles = use(articlesPromise);
  return (
    <ul>
      {articles.map(a => (
        <li key={a.id}>{a.title}</li>
      ))}
    </ul>
  );
}

export function Articles() {
  return (
    <Suspense fallback={<p>Chargement…</p>}>
      <ListeArticles />
    </Suspense>
  );
}

use(promise) suspend le rendu jusqu’à la résolution de la promesse, et le composant Suspense englobant affiche un fallback pendant l’attente. Aucun useState, aucun useEffect, aucune gestion manuelle de l’état loading. Pour la production, on englobe avec React Query ou SWR qui ajoutent cache, revalidation et gestion d’erreur, tout en restant compatibles avec le pattern Suspense.

Étape 5 — Formulaires modernes avec useActionState

Le pattern classique useState + onSubmit + état de chargement manuel est remplacé par useActionState. La fonction d’action reçoit l’état précédent et le FormData, retourne le nouvel état, et React gère la transition pendante automatiquement.

// src/components/Inscription.tsx
import { useActionState } from "react";

type State = { error?: string; success?: boolean };

async function inscrire(_prev: State, formData: FormData): Promise<State> {
  const email = formData.get("email") as string;
  const res = await fetch("/api/inscription", {
    method: "POST",
    body: JSON.stringify({ email }),
    headers: { "Content-Type": "application/json" },
  });
  if (!res.ok) return { error: "Email déjà utilisé" };
  return { success: true };
}

export function Inscription() {
  const [state, action, pending] = useActionState<State, FormData>(inscrire, {});
  return (
    <form action={action}>
      <input name="email" type="email" required />
      <button disabled={pending}>{pending ? "Envoi…" : "S'inscrire"}</button>
      {state.error && <p style={{color:"red"}}>{state.error}</p>}
      {state.success && <p>Inscription enregistrée.</p>}
    </form>
  );
}

Le tuple retourné par useActionState contient l’état courant, la fonction à passer en action du formulaire, et un booléen pending qui passe à true pendant l’exécution. Le formulaire est désactivé automatiquement pendant la soumission, sans gérer manuellement setIsLoading. Pour des messages d’erreur granulaires, on enrichit le type State avec un objet fieldErrors et l’action retourne les erreurs par champ.

Étape 6 — Activer le React Compiler

Le React Compiler analyse votre code à la compilation et insère automatiquement les memoizations nécessaires. Le résultat : les composants ne se re-render que quand leurs entrées changent réellement, sans useMemo ni useCallback manuels.

pnpm add -D babel-plugin-react-compiler
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          ["babel-plugin-react-compiler", { target: "19" }],
        ],
      },
    }),
  ],
});

Une fois le compiler activé, supprimer progressivement les useMemo et useCallback superflus. L’extension React DevTools 6 colore les composants en vert/rouge selon que le compiler les a optimisés ou non — outil indispensable pour vérifier la couverture sur un projet existant. Sur un dashboard typique, le compiler élimine 90 % des re-renders inutiles sans modifier le code applicatif.

Étape 7 — Server Components (aperçu)

Les Server Components ne sont pas exécutables avec une SPA Vite : ils nécessitent un framework qui orchestre les requêtes serveur, comme Next.js 15 ou Waku. Le concept clé est qu’un composant marqué server peut await une requête base de données et le résultat est sérialisé pour le navigateur.

// app/articles/page.tsx (Next.js 15)
import { db } from "@/lib/db";

export default async function ArticlesPage() {
  const articles = await db.article.findMany({ where: { publie: true } });
  return (
    <ul>
      {articles.map(a => (
        <li key={a.id}>{a.titre}</li>
      ))}
    </ul>
  );
}

Aucun useState, aucun fetch, aucune API REST intermédiaire : le composant parle directement à la base de données et envoie le HTML rendu au navigateur. Les composants interactifs (formulaires, animations) restent côté client et sont marqués par la directive "use client" en première ligne du fichier. Cette dualité est désormais le modèle dominant pour les nouvelles applications React full-stack.

Erreurs fréquentes

Symptôme Cause Solution
Re-render à l’infini setState dans le corps du composant Mettre dans useEffect ou un handler
« Cannot update during rendering » setState d’un parent depuis l’enfant pendant le render Lifter via callback ou useEffect
Hooks appelés conditionnellement Violation de la règle des hooks Toujours appeler dans le même ordre
useEffect qui s’exécute deux fois en dev StrictMode (intentionnel) Rendre l’effet idempotent ou désactiver StrictMode en dernier recours
État non mis à jour immédiatement setState est asynchrone et batché Utiliser la valeur dans un effect ou la forme fonction
« use is not a function » Version React < 19 Mettre à jour vers React 19+

Sur le même thème

مشاركة