🚀 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
"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
"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éthode | useOptimistic | revalidatePath |
---|---|---|
🎯 Effet | Mise à jour immédiate | Rafraîchit la page après mutation |
⚡ Performance | Instantané | Plus lent (attend la réponse serveur) |
🔄 Expérience utilisateur | Ultra réactif | Parfois des flashs de chargement |
✅ Utilisation idéale | Modifications visuelles rapides | Donné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.
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.