UI optimiste et revalidation des données
Utilise useOptimistic, useTransition et revalidatePath pour une UX réactive.
Accéder gratuitement à cette formation
Renseigne ton email pour débloquer immédiatement cette formation gratuite.
Dans cette leçon, tu vas découvrir comment fluidifier l’expérience utilisateur en appliquant une UI optimiste pour la suppression d’éléments de l’historique, et comment forcer la revalidation des données côté serveur après une mutation. Au programme :
- Pourquoi opter pour une UI optimiste
- Mise en place de
useOptimisticdansHistoryList - Gestion de la mutation côté client avec
useTransition - Forcer la revalidation via
revalidatePath - Points clés et exercices pratiques
Pourquoi une UI optimiste ?
Lorsque l’utilisateur supprime un élément de son historique, un aller-retour vers le serveur peut introduire un délai perceptible.
Avec une UI optimiste, on met à jour l’interface immédiatement, avant même que le serveur ait confirmé la suppression.
Le résultat : une application plus réactive et un ressenti utilisateur sans latence.
UX améliorée
L’UI optimiste réduit la frustration liée aux temps d’attente et donne l’illusion d’une appli locale ultra-rapide.
1. useOptimistic dans le composant HistoryList
Le conteneur client HistoryList reçoit la liste initiale et instancie useOptimistic.
Il fournit une fonction de mise à jour optimiste qui filtre l’item supprimé avant la réponse réelle.
1'use client'2import { Suspense, useOptimistic } from 'react'3import { getHistory } from '../server/history'4import { HistoryMediaItem } from './HistoryMediaItem'56export function HistoryList({7initialHistoryItems,8user,9}: {10initialHistoryItems: Awaited<ReturnType<typeof getHistory>>11user: { email: string }12}) {13// on stocke la liste et la fonction de suppression optimiste14const [historyItems, removeOptimistic] = useOptimistic<15Awaited<ReturnType<typeof getHistory>>,16string17>(18initialHistoryItems,19(current, removedId) => current.filter(item => item.id !== removedId)20)2122return (23<div>24<h1>Watch History</h1>25<ul>26{historyItems.map(item => (27<HistoryMediaItem28key={item.id}29item={item}30user={user}31removeHistoryItemOptimisticly={removeOptimistic}32/>33))}34</ul>35</div>36)37}
initialHistoryItems: données chargées au rendu serveurhistoryItems: état local mis à jour immédiatementremoveOptimistic: fonction qu’on appelle avant la requête réelle
2. Mutation côté client avec useTransition
Dans chaque carte (movie ou serie), le bouton de suppression déclenche deux actions :
- On applique la suppression optimiste en appelant
removeOptimistic(itemId). - On engage la requête serveur via
removeHistoryItem({ historyItemId }).
Le hook useTransition permet de désactiver le bouton pendant la mutation.
1'use client'2import { useTransition } from 'react'3import { removeHistoryItem } from '../server/movies'45export function RemoveHistoryItemButton({6historyItemId,7removeHistoryItemOptimisticly,8}: {9historyItemId: string10removeHistoryItemOptimisticly: (id: string) => void11}) {12const [isPending, startTransition] = useTransition()1314return (15<button16disabled={isPending}17onClick={() => {18startTransition(() => {19// 1. UI optimiste20removeHistoryItemOptimisticly(historyItemId)21// 2. appel serveur22removeHistoryItem({ historyItemId })23})24}}25>26{isPending ? 'Suppression…' : 'Supprimer'}27</button>28)29}
Ici, on ne bloque pas l’UI, on limite seulement la ré-appui jusqu’à retour réseau.
3. Revalidation côté serveur avec revalidatePath
Après suppression réelle, Next.js peut servir des données mises en cache.
Pour s’assurer que la page /history se rafraîchit, on appelle revalidatePath('/history').
1'use server'2import { prisma } from './db'3import { requireUser } from './session'4import { revalidatePath } from 'next/cache'56export async function removeHistoryItem({7historyItemId,8}: {9historyItemId: string10}) {11await requireUser()1213// suppression en base14await prisma.historyItem.delete({15where: { id: historyItemId },16})1718// force la revalidation de la route /history19revalidatePath('/history')20}
1- // suppression en base2- await prisma.historyItem.delete({ where: { id: historyItemId } })3+ // suppression en base4+ await prisma.historyItem.delete({ where: { id: historyItemId } })5+ // ^? Forcer une réactualisation du cache67- // retour implicite8+ revalidatePath('/history')
Cache stale
Sans revalidatePath, Next.js pourrait renvoyer une ancienne liste d’historique.
Points clés
- useOptimistic permet de mettre à jour immédiatement l’UI
- startTransition coordonne l’optimisme et la mutation serveur
- revalidatePath invalide le cache Next.js pour des données toujours fraîches
- L’optimisme doit pouvoir revenir en arrière en cas d’erreur réseau (à prévoir)
Exercices rapides
- Ajouter l’optimisme à la création
- Dans
AddHistoryItemButton, utiliseuseOptimisticpour insérer l’item avant le retour serveur.
- Dans
- Gérer l’échec réseau
- Simule une erreur côté
removeHistoryItemet restaure l’élément dans l’UI.
- Simule une erreur côté
- Revalidation conditionnelle
- Apporte
revalidateTagsur un tag personnalisé plutôt que sur un chemin fixe.
- Apporte
Passe à l’action et amuse-toi bien !