đ 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
"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
"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 étatisPending
qui devienttrue
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Ă©thode | useTransition() | useState() |
---|---|---|
đŻ Effet | Laisse lâUI rĂ©active | Peut bloquer lâUI |
⥠Performance | Mutation non bloquante | Risque de lag |
đ Utilisation idĂ©ale | Formulaires, interactions lĂ©gĂšres | Chargement 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 !
"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>
);
}
"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.