Skip to Content
🎉 Utilisez JS efficacement →

🚀 Gestion des mutations avec useTransition() dans Next.js 13+

Lorsqu’on effectue des mutations (ajout, suppression, mise Ă  jour de donnĂ©es), il est crucial d’assurer une bonne expĂ©rience utilisateur sans ralentir l’interface.
👉 C’est là que useTransition() entre en jeu !


1ïžâƒŁ Pourquoi utiliser useTransition() ?

✅ Avantages :

  • EmpĂȘche le blocage de l’interface lors des mutations.
  • Permet d’afficher un Ă©tat de chargement lĂ©ger.
  • OptimisĂ© pour les interactions non critiques (ex: soumission d’un formulaire).
  • Compatible avec les Server Actions.

❌ Sans useTransition() :

  • L’UI se fige lors d’une action lourde (ex: envoi d’un formulaire).
  • L’expĂ©rience utilisateur est dĂ©gradĂ©e sur des rĂ©seaux lents.

2ïžâƒŁ Exemple : Ajouter une tĂąche avec useTransition()

Nous allons implĂ©menter un formulaire d’ajout de tĂąche qui :
1ïžâƒŁ EmpĂȘche le blocage du bouton lors de l’envoi.
2ïžâƒŁ Affiche un Ă©tat de chargement temporaire.


📌 Étape 1 : Mise à jour de l’action serveur

app/actions/todos.ts
"use server"; import { db } from "@/lib/db"; import { revalidatePath } from "next/cache"; export async function addTodo(title: string) { if (!title) return { error: "Le titre est requis" }; await new Promise((resolve) => setTimeout(resolve, 1000)); // Simule un délai réseau await db.todo.create({ data: { title } }); revalidatePath("/todos"); // Rafraßchit la page return { success: "Tùche ajoutée !" }; }

✅ Ajout d’un setTimeout() pour simuler une requĂȘte lente.


📌 Étape 2 : Utilisation de useTransition() dans le formulaire

app/todos/AddTodoForm.tsx
"use client"; import { useState, useTransition } from "react"; import { addTodo } from "@/app/actions/todos"; export default function AddTodoForm() { const [title, setTitle] = useState(""); const [isPending, startTransition] = useTransition(); // đŸ”„ useTransition async function handleSubmit(event: React.FormEvent) { event.preventDefault(); startTransition(async () => { const result = await addTodo(title); if (result?.success) setTitle(""); // RĂ©initialise le champ en cas de succĂšs }); } return ( <form onSubmit={handleSubmit} className="mt-4 space-y-3"> <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Nouvelle tĂąche" className="w-full p-2 border rounded" required /> <button type="submit" disabled={isPending} className={`w-full py-2 rounded ${isPending ? "bg-gray-400" : "bg-green-500 text-white"}`} > {isPending ? "Ajout..." : "Ajouter"} </button> </form> ); }

✅ Explication :

  • useTransition() crĂ©e un Ă©tat isPending qui devient true pendant la mutation.
  • startTransition(callback) exĂ©cute la mutation sans bloquer l’interface.
  • Le bouton devient dĂ©sactivĂ© (disabled) et affiche "Ajout..." pendant le traitement.

3ïžâƒŁ Comparaison : useTransition() vs useState()

đŸ”č MĂ©thodeuseTransition()useState()
🎯 EffetLaisse l’UI rĂ©activePeut bloquer l’UI
⚡ PerformanceMutation non bloquanteRisque de lag
🔄 Utilisation idĂ©aleFormulaires, interactions lĂ©gĂšresChargement global, mises Ă  jour frĂ©quentes

4ïžâƒŁ Exemple avancĂ© : Combiner useOptimistic et useTransition()

On peut combiner useOptimistic() et useTransition() pour une UX fluide et instantanée !

app/todos/page.tsx
"use client"; import { useOptimistic, useTransition, useState } from "react"; import { getTodos, addTodo } from "@/app/actions/todos"; export default function TodosPage() { const [todos, setTodos] = useState(await getTodos()); const [isPending, startTransition] = useTransition(); const [optimisticTodos, setOptimisticTodos] = useOptimistic( todos, (currentTodos, newTodo) => [...currentTodos, newTodo] // Ajout instantané ); function handleAdd(title: string) { const newTodo = { id: Date.now().toString(), title }; // Ajout optimiste setOptimisticTodos(newTodo); // Mise à jour instantanée startTransition(async () => { await addTodo(title); // Envoi réel }); } 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="border-b py-2">{todo.title}</li> ))} </ul> <AddTodoForm onAdd={handleAdd} isPending={isPending} /> </div> ); }
app/todos/AddTodoForm.tsx
"use client"; import { useState } from "react"; export default function AddTodoForm({ onAdd, isPending }: { onAdd: (title: string) => void, isPending: boolean }) { const [title, setTitle] = useState(""); function handleSubmit(event: React.FormEvent) { event.preventDefault(); onAdd(title); setTitle(""); } return ( <form onSubmit={handleSubmit} className="mt-4 space-y-3"> <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Nouvelle tĂąche" className="w-full p-2 border rounded" required /> <button type="submit" disabled={isPending} className={`w-full py-2 rounded ${isPending ? "bg-gray-400" : "bg-green-500 text-white"}`} > {isPending ? "Ajout..." : "Ajouter"} </button> </form> ); }

✅ Effet combinĂ© :

  • useOptimistic() affiche instantanĂ©ment la nouvelle tĂąche.
  • useTransition() Ă©vite de bloquer l’interface pendant l’envoi rĂ©el.

🎯 Conclusion : Pourquoi utiliser useTransition() ?

🚀 Avec useTransition(), l’expĂ©rience utilisateur est optimisĂ©e :
✅ Mutation fluide → Aucune latence perçue.
✅ Interface toujours rĂ©active → Pas de blocage des boutons.
✅ Facile Ă  intĂ©grer avec useOptimistic() → Interactions instantanĂ©es.

mis Ă  jour le