Utilise useOptimistic, useTransition et revalidatePath pour une UX réactive.
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 :
useOptimistic
dans HistoryList
useTransition
revalidatePath
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.
L’UI optimiste réduit la frustration liée aux temps d’attente et donne l’illusion d’une appli locale ultra-rapide.
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éelleuseTransition
Dans chaque carte (movie ou serie), le bouton de suppression déclenche deux actions :
removeOptimistic(itemId)
.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.
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')
Sans revalidatePath
, Next.js pourrait renvoyer une ancienne liste d’historique.
AddHistoryItemButton
, utilise useOptimistic
pour insérer l’item avant le retour serveur.removeHistoryItem
et restaure l’élément dans l’UI.revalidateTag
sur un tag personnalisé plutôt que sur un chemin fixe.Passe à l’action et amuse-toi bien !