Tous les articles
·Ingénierie·8 min

Les nouveaux hooks React 19 : use, useOptimistic et useActionState

Guide pratique des quatre nouveaux hooks React 19 dans Next.js App Router : use(), useActionState, useOptimistic et useFormStatus pour des formulaires modernes.


title: "Les nouveaux hooks React 19 : use, useOptimistic et useActionState" description: "Guide pratique des quatre nouveaux hooks React 19 dans Next.js App Router : use(), useActionState, useOptimistic et useFormStatus pour des formulaires modernes." date: "2026-06-07" category: "Ingénierie" readingTime: "8 min" author: "Oliwer Fortin, Tech Lead chez Kreio"

React 19 officialise quatre hooks qui simplifient la gestion des données asynchrones et des formulaires dans les projets Next.js App Router : use(), useActionState, useOptimistic et useFormStatus. Voici comment les adopter concrètement en production, sans dépendance tierce supplémentaire.

Développeur écrivant du code React 19 avec les nouveaux hooks
React 19 centralise la gestion d'état asynchrone dans des hooks natifs, réduisant le besoin de librairies tierces pour les cas courants.

Pourquoi React 19 change la gestion des formulaires

Avant React 19, un formulaire connecté à une API nécessitait au minimum trois états locaux distincts : loading, error et data. Chaque mutation était un assemblage de useState, useEffect et de logique de gestion d'erreur inlined dans le composant. Le code produit était verbeux et difficile à tester.

React 19 introduit le concept d'Actions. Une Action est une fonction asynchrone passée directement à l'attribut action d'un élément <form>, ou utilisée dans une transition useTransition. Elle peut s'exécuter côté client ou côté serveur (via les Server Actions de Next.js). Les nouveaux hooks ont été conçus pour fonctionner nativement avec ce modèle.

SituationAvant React 19Avec React 19
État pending d'un formulaireuseState(false) pour isLoadinguseFormStatus lit l'état automatiquement
Retour d'erreur d'une Actiontry/catch + setState dans le composantuseActionState expose l'état retourné par l'Action
Feedback optimiste immédiatMise à jour manuelle avec rollback à géreruseOptimistic gère l'état temporaire et le rollback
Lecture conditionnelle d'un contexteToujours au niveau racine du composantuse(Context) utilisable dans les branches conditionnelles

Le hook use() : Promises et contextes conditionnels

use() est le seul hook de React 19 dont l'usage peut être conditionnel. Il peut être appelé à l'intérieur d'un bloc if, d'une boucle ou d'un callback, ce qui était interdit par les règles des hooks depuis React 16.8.

Son principal cas d'usage dans Next.js App Router : transmettre une Promise non résolue depuis un Server Component vers un Client Component.

// app/dashboard/page.tsx (Server Component)
import { Suspense } from 'react'
import UserList from './user-list'

export default function DashboardPage() {
  const usersPromise = fetchUsers() // Pas d'await : la Promise est transmise telle quelle

  return (
    <Suspense fallback={<p>Chargement des utilisateurs...</p>}>
      <UserList usersPromise={usersPromise} />
    </Suspense>
  )
}
// app/dashboard/user-list.tsx (Client Component)
'use client'
import { use } from 'react'
import type { User } from '@/types'

type Props = { usersPromise: Promise<User[]> }

export default function UserList({ usersPromise }: Props) {
  const users = use(usersPromise) // React suspend jusqu'à résolution

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

Ce pattern déporte le déclenchement du fetch au niveau serveur tout en conservant l'interactivité côté client. La documentation officielle React sur use() détaille les règles de stabilité des Promises à respecter.

useActionState : gérer l'état retourné par une Server Action

useActionState remplace le pattern useState + handler manuel pour les formulaires connectés à des Server Actions. Il prend une Action et un état initial, et retourne l'état courant, une version wrappée de l'Action, et un booléen isPending.

// components/contact-form.tsx
'use client'
import { useActionState } from 'react'
import { sendContactForm } from '@/actions/contact'

type FormState = { success: boolean; error?: string }
const initialState: FormState = { success: false }

export default function ContactForm() {
  const [state, action, isPending] = useActionState(sendContactForm, initialState)

  return (
    <form action={action}>
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Envoi en cours...' : 'Envoyer'}
      </button>
      {state.error && <p role="alert">{state.error}</p>}
      {state.success && <p>Message envoyé avec succès.</p>}
    </form>
  )
}

La Server Action reçoit l'état précédent en premier argument, avant formData. Ce contrat est obligatoire pour que useActionState puisse chaîner les états :

// actions/contact.ts
'use server'
import type { FormState } from '@/types'

export async function sendContactForm(
  prevState: FormState,
  formData: FormData
): Promise<FormState> {
  const email = formData.get('email') as string
  const message = formData.get('message') as string

  if (!email || !message) {
    return { success: false, error: 'Tous les champs sont obligatoires.' }
  }

  await emailService.send({ to: email, body: message })
  return { success: true }
}

Le gain est direct : le composant n'a plus besoin de gérer isLoading, ni d'intercepter les erreurs réseau. L'état d'erreur remonte du serveur vers le client via la valeur de retour de l'Action, sans serialisation manuelle.

useOptimistic : feedback instantané avant confirmation serveur

useOptimistic permet d'afficher immédiatement le résultat attendu d'une mutation. Si la requête échoue, React revient automatiquement à l'état précédent, sans code de rollback à écrire.

La logique repose sur une simple observation : si l'opération prend tt millisecondes côté réseau, l'utilisateur perçoit un délai de 00 ms grâce à la mise à jour optimiste, contre tt ms sans ce pattern. Sur une latence mobile typique de 200 à 400 ms, l'impact sur la fluidité perçue est significatif.

'use client'
import { useOptimistic, useTransition } from 'react'
import { toggleFavorite } from '@/actions/favorites'
import type { Article } from '@/types'

export default function FavoriteButton({ article }: { article: Article }) {
  const [optimisticArticle, setOptimistic] = useOptimistic(
    article,
    (current, isFavorite: boolean) => ({
      ...current,
      isFavorite,
      favoriteCount: current.favoriteCount + (isFavorite ? 1 : -1),
    })
  )
  const [, startTransition] = useTransition()

  function handleToggle() {
    startTransition(async () => {
      setOptimistic(!optimisticArticle.isFavorite)
      await toggleFavorite(article.id)
    })
  }

  return (
    <button onClick={handleToggle} aria-pressed={optimisticArticle.isFavorite}>
      {optimisticArticle.isFavorite ? 'Retirer des favoris' : 'Ajouter aux favoris'}
      ({optimisticArticle.favoriteCount})
    </button>
  )
}

La documentation React sur useOptimistic recommande de toujours envelopper l'appel dans startTransition pour éviter des états incohérents entre le rendu optimiste et la réponse serveur.

useFormStatus : lire l'état de soumission depuis un composant enfant

useFormStatus est importé depuis react-dom et non depuis react. Il lit l'état de soumission du formulaire parent le plus proche dans l'arbre de rendu. Son usage canonique est la création d'un bouton de soumission réutilisable et accessible :

// components/submit-button.tsx
'use client'
import { useFormStatus } from 'react-dom'

type Props = { label: string; pendingLabel?: string }

export function SubmitButton({ label, pendingLabel = 'En cours...' }: Props) {
  const { pending } = useFormStatus()

  return (
    <button type="submit" disabled={pending} aria-busy={pending}>
      {pending ? pendingLabel : label}
    </button>
  )
}

Le composant SubmitButton s'utilise ensuite dans n'importe quel formulaire sans passer isPending en prop :

<form action={action}>
  <input name="name" type="text" />
  <SubmitButton label="Sauvegarder" pendingLabel="Sauvegarde en cours..." />
</form>

Récapitulatif des quatre hooks

HookModuleCas d'usage principal
use()reactLire une Promise ou un Context conditionnellement
useActionStatereactGérer l'état et les erreurs d'une Action de formulaire
useOptimisticreactAfficher un résultat immédiat avant confirmation serveur
useFormStatusreact-domLire le statut de soumission depuis un composant enfant

En pratique

React 19 ne remplace pas React Query ou SWR pour les cas complexes : cache côté client persistant, requêtes paginées avec curseur, invalidation fine par clé, ou synchronisation en temps réel. En revanche, pour les formulaires connectés à des Server Actions dans Next.js App Router, useActionState et useFormStatus sont désormais le choix natif. Ils réduisent le nombre de lignes de code, améliorent l'accessibilité via les attributs aria-busy et aria-disabled, et s'intègrent sans friction au modèle serveur de Next.js.

Pour les mutations avec feedback immédiat, useOptimistic élimine le code de rollback manuel. Le hook use(), de son côté, simplifie les patterns de streaming entre Server Components et Client Components. Sur les pages à fort contenu dynamique, déporter le fetch côté serveur via une Promise non résolue réduit le Time to First Byte et améliore les Core Web Vitals.

La migration est progressive : adopter useFormStatus dans un formulaire existant ne nécessite pas de refactoriser l'ensemble de l'application.

Sources

  1. React v19, notes de version officielles
  2. useFormStatus, documentation React
  3. useOptimistic, documentation React