Skip to Content
🎉 Utilisez JS efficacement →

🚀 Gestion des mutations avec useOptimistic dans Next.js 13+

L’optimistic UI améliore l’expérience utilisateur en affichant immédiatement une mise à jour de l’interface avant même la fin du traitement serveur.
Dans Next.js 13+, on utilise useOptimistic pour gérer ces mutations côté client tout en tirant parti des Server Actions.


1️⃣ Pourquoi utiliser useOptimistic ?

Avantages :

  • Affichage instantané des mises à jour sans attendre la réponse serveur.
  • Meilleure expérience utilisateur (réactivité accrue).
  • Evite les flashs de chargement entre l’envoi et la mise à jour réelle.

Sans useOptimistic :

  • L’interface attend la réponse serveur avant de s’actualiser → latence perçue.
  • Besoin de rafraîchir les données via revalidatePath() après chaque mutation.

2️⃣ Exemple : Liste de tâches avec mise à jour optimiste

Nous allons créer une application de gestion de tâches avec :
1️⃣ Un affichage en temps réel des tâches.
2️⃣ Une suppression optimiste avant confirmation serveur.


📌 Étape 1 : Server Actions pour récupérer et supprimer une tâche

app/actions/todos.ts
"use server"; import { db } from "@/lib/db"; import { revalidatePath } from "next/cache"; // Récupérer les tâches export async function getTodos() { return await db.todo.findMany(); } // Supprimer une tâche export async function deleteTodo(id: string) { await db.todo.delete({ where: { id } }); // Forcer la mise à jour côté serveur revalidatePath("/todos"); }

Explication :

  • getTodos() récupère les tâches.
  • deleteTodo() supprime une tâche et rafraîchit automatiquement la liste.

📌 Étape 2 : Affichage avec useOptimistic

app/todos/page.tsx
"use client"; import { useOptimistic, useState } from "react"; import { getTodos, deleteTodo } from "@/app/actions/todos"; export default function TodosPage() { const [todos, setTodos] = useState(await getTodos()); // 🎯 Utilisation de `useOptimistic` const [optimisticTodos, setOptimisticTodos] = useOptimistic( todos, // Valeur initiale (currentTodos, idToRemove) => currentTodos.filter(todo => todo.id !== idToRemove) ); async function handleDelete(id: string) { // Mise à jour immédiate de la liste (optimiste) setOptimisticTodos(id); // Envoi réel au serveur await deleteTodo(id); } return ( <div className="max-w-md mx-auto p-4 border rounded-lg shadow-lg"> <h1 className="text-xl font-bold mb-4">Liste des tâches</h1> <ul> {optimisticTodos.map((todo) => ( <li key={todo.id} className="flex justify-between border-b py-2"> {todo.title} <button onClick={() => handleDelete(todo.id)} className="text-red-500" > </button> </li> ))} </ul> </div> ); }

Explication :

  • useOptimistic(todos, callback) permet de supprimer immédiatement une tâche de la liste.
  • Dès le clic, la tâche disparaît avant même que deleteTodo() soit exécuté.
  • Si la suppression échoue, on peut gérer un rollback (ex. afficher un message d’erreur).

3️⃣ Comparaison : useOptimistic vs revalidatePath

🔹 MéthodeuseOptimisticrevalidatePath
🎯 EffetMise à jour immédiateRafraîchit la page après mutation
PerformanceInstantanéPlus lent (attend la réponse serveur)
🔄 Expérience utilisateurUltra réactifParfois des flashs de chargement
Utilisation idéaleModifications visuelles rapidesDonnées critiques nécessitant une revalidation

4️⃣ Gérer les erreurs (rollback en cas d’échec)

Que se passe-t-il si la suppression échoue ?
On peut ajouter une gestion d’erreur avec un rollback.

handleDelete()
async function handleDelete(id: string) { const previousTodos = optimisticTodos; // Sauvegarde setOptimisticTodos(id); // Suppression instantanée try { await deleteTodo(id); } catch (error) { setOptimisticTodos(previousTodos); // Rollback en cas d'erreur } }

Si deleteTodo(id) échoue, la tâche réapparaît immédiatement.


🎯 Conclusion : Pourquoi useOptimistic est un game-changer ?

🚀 Avec useOptimistic, l’UI devient ultra-fluide :
Moins de latence perçue → meilleures performances UX.
Moins de re-rendering inutile.
Gestion fine des mutations sans bloquer l’interface.

mis à jour le