Skip to Content
🎉 Utilisez JS efficacement →

📌 Travail Pratique : Server Actions & Optimistic UI avec Next.js 14+

Objectifs

  1. Implémenter un formulaire de contact utilisant une Server Action pour envoyer des données sans passer par un appel fetch() explicite.
  2. CrĂ©er un bouton “Like” qui met Ă  jour instantanĂ©ment l’affichage (optimistic UI) grĂące Ă  useOptimistic() et useTransition().

Note : Dans Next.js 14+, les Server Actions permettent d’attacher directement une fonction serveur Ă  l’attribut action d’un formulaire. Assurez-vous que les fichiers respectent la convention « "use server"; en tĂȘte de fichier d’action ».

🔗 Code source : 📂 GitHub  🚀


📝 Exercice 1 : Formulaire de contact avec Server Action

Étape 1 : CrĂ©er la Server Action

Créez un fichier app/contact/contact.ts qui contient la fonction serveur suivante :

app/contact/contact.ts
"use server"; import { revalidatePath } from "next/cache"; export async function sendContactMessage(formData: FormData) { // Récupération et validation des données const name = formData.get("name") as string; const email = formData.get("email") as string; const message = formData.get("message") as string; if (!name || !email || !message) { return { error: "Tous les champs sont requis." }; } // Simulation d'un délai réseau (1 seconde) await new Promise((resolve) => setTimeout(resolve, 1000)); // Log en console cÎté serveur (pour la démo) console.log("Message reçu :", { name, email, message }); // Optionnel : invalider le cache de la page /contact pour rafraßchir les données revalidatePath("/contact"); return { success: "Message envoyé !" }; }

Explication :

  • La directive "use server"; garantit que cette fonction s’exĂ©cute uniquement cĂŽtĂ© serveur.
  • La fonction rĂ©cupĂšre les champs du formulaire, effectue une validation simple et simule un dĂ©lai.
  • En cas de succĂšs, elle renvoie un objet avec une propriĂ©tĂ© success, sinon une propriĂ©tĂ© error.

Étape 2 : CrĂ©er le formulaire cĂŽtĂ© client

CrĂ©ez ou modifiez le fichier app/contact/page.tsx pour utiliser la Server Action directement via l’attribut action du formulaire. Comme l’interactivitĂ© est gĂ©rĂ©e cĂŽtĂ© client (affichage des messages, gestion de l’état), le composant doit ĂȘtre un Client Component (dĂ©clarĂ© avec "use client").

app/contact/page.tsx
"use client"; import { useState, useTransition } from "react"; import { sendContactMessage } from "@/app/contact/contact"; import { useRouter } from "next/navigation"; export default function ContactPage() { const [feedback, setFeedback] = useState<string | null | undefined>(null); const [isPending, startTransition] = useTransition(); const router = useRouter() // Nous utilisons la Server Action directement dans le formulaire. // Next.js 14+ gĂšre automatiquement la conversion du FormData en appel serveur. async function handleSubmit(event: React.FormEvent<HTMLFormElement>) { event.preventDefault(); // DĂ©marrer la transition pour Ă©viter de bloquer l'UI startTransition(async () => { // Appel direct de la Server Action via l'attribut "action" const result = await sendContactMessage(new FormData(event.currentTarget)); if (result?.success) { setFeedback(result.success); router.refresh(); } else { setFeedback(result.error); } }); } return ( <div className="max-w-lg mx-auto p-6 border rounded-lg shadow-lg"> <h1 className="text-xl font-bold mb-4">Contactez-nous</h1> <form onSubmit={handleSubmit} className="space-y-3"> <input name="name" placeholder="Nom" required className="w-full p-2 border rounded" /> <input type="email" name="email" placeholder="Email" required className="w-full p-2 border rounded" /> <textarea name="message" placeholder="Votre message" required className="w-full p-2 border rounded" /> <button type="submit" disabled={isPending} className={`w-full py-2 rounded ${isPending ? "bg-gray-400" : "bg-blue-500 text-white" }`} > {isPending ? "Envoi en cours..." : "Envoyer"} </button> </form> {feedback && <p className="mt-2 text-center text-green-500">{feedback}</p>} </div> ); }

Explication :

  • Le hook useTransition() permet de lancer la fonction sans bloquer l’interface.
  • L’état feedback affiche un message de succĂšs ou d’erreur aprĂšs l’envoi.

📝 Exercice 2 : Bouton “Like” avec mise à jour optimiste

Étape 1 : CrĂ©er la Server Action pour incrĂ©menter le compteur

Créez le fichier app/likes/likes.ts :

app/likes/likes.ts
"use server"; import { revalidatePath } from "next/cache"; // Simuler un compteur stocké en mémoire (à remplacer par une DB dans une vraie application) let likeCount = 0; export async function addLike() { likeCount++; // Simuler un délai de traitement (500 ms) await new Promise((resolve) => setTimeout(resolve, 500)); // Optionnel : invalider la page /likes pour rafraßchir le contenu revalidatePath("/likes"); return likeCount; }

Explication :

  • La fonction incrĂ©mente une variable likeCount et simule un dĂ©lai.
  • Elle renvoie le nouveau nombre de likes aprĂšs mise Ă  jour.

Étape 2 : CrĂ©er l’interface du bouton Like avec mise Ă  jour optimiste

Créez ou modifiez le fichier app/likes/page.tsx :

app/likes/page.tsx
"use client"; import { useState, useTransition, useOptimistic } from "react"; import { addLike } from "@/app/likes/likes"; export default function LikeButton() { const [likes, setLikes] = useState(0); const [isPending, startTransition] = useTransition(); // Utilisation de useOptimistic pour mettre Ă  jour immĂ©diatement le compteur const [optimisticLikes, setOptimisticLikes] = useOptimistic(likes, (current) => current + 1); function handleLike() { // Lancer la Server Action dans une transition pour Ă©viter le blocage de l'UI startTransition(async () => { // Mise Ă  jour optimiste immĂ©diate setOptimisticLikes(likes); const updatedLikes = await addLike(); setLikes(updatedLikes); // Met Ă  jour l'Ă©tat rĂ©el une fois la rĂ©ponse obtenue }); } return ( <div className="flex flex-col items-center p-6 border rounded-lg shadow-lg"> <button onClick={handleLike} disabled={isPending} className={`px-4 py-2 rounded ${isPending ? "bg-gray-400" : "bg-red-500 text-white"}`} > ❀ Like ({optimisticLikes}) </button> </div> ); }

Explication :

  • Le hook useOptimistic() permet d’afficher immĂ©diatement l’incrĂ©mentation du compteur, amĂ©liorant ainsi la rĂ©activitĂ© (optimistic UI).
  • useTransition() garantit que l’appel Ă  la Server Action ne bloque pas l’interface.
  • Une fois la Server Action exĂ©cutĂ©e, l’état rĂ©el (likes) est mis Ă  jour.

🎯 Conclusion

Avec cet exercice, vous avez appris Ă  :

  • Utiliser les Server Actions de Next.js 14+ pour gĂ©rer des formulaires sans recourir Ă  un appel fetch() explicite.
  • Mettre en place une optimistic UI avec useOptimistic() et useTransition(), garantissant ainsi une expĂ©rience utilisateur fluide et rĂ©active.

N’oubliez pas de tester vos implĂ©mentations en local et de consulter la documentation officielle pour les derniĂšres mises Ă  jour sur les Server Actions et les hooks expĂ©rimentaux. Bonne pratique !

mis Ă  jour le