Ajout et suppression d’éléments de l’historique
Ajoute des films/séries à l’historique et supprime-les avec Server Actions.
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 gérer la logique d’ajout et de suppression dans l’historique utilisateur, tout en apportant une UI optimiste et une revalidation automatique des données. On va couvrir :
- Le modèle Prisma et la migration
- Les Server Actions pour l’historique
- Les boutons client (
AddHistoryItemButton,RemoveHistoryItemButton) - La liste d’historique avec
useOptimisticetuseTransition - La revalidation forcée via
revalidatePath
Contexte
On part du projet Next.js 15 full-stack (RSC + Server Actions + Prisma).
Le modèle HistoryItem a déjà été ajouté et migré.
Modèle Prisma et migration
On définit un modèle HistoryItem dans schema.prisma :
1model HistoryItem {2id Int @id @default(autoincrement())3user User @relation(fields: [userId], references: [id])4userId Int5movieId Int?6serieId Int?7createdAt DateTime @default(now())8}
Puis on génère la migration :
1npx prisma migrate dev --name add-history-item
Server Actions pour l’historique
On crée des actions serveur pour ajouter, lister et supprimer un item d’historique.
1'use server';23import { prisma } from './db';4import { requireUser } from './session';5import { getMovie, getSerie } from './movies';67// Ajouter un élément à l’historique8export async function addHistoryItem({9itemId,10type,11}: {12itemId: number;13type: 'movie' | 'serie';14}) {15'use server' // ^? indique une Server Action16const user = await requireUser();1718return await prisma.historyItem.create({19data: {20user: { connect: { id: user.id } },21...(type === 'movie' && { movieId: itemId }),22...(type === 'serie' && { serieId: itemId }),23},24});25}2627// Récupérer l’historique complet de l’utilisateur28export async function getHistory() {29'use server'30const user = await requireUser();31return await prisma.historyItem.findMany({32where: { userId: user.id },33orderBy: { createdAt: 'desc' },34});35}3637// Supprimer un élément de l’historique38export async function removeHistoryItem({39historyItemId,40}: {41historyItemId: number;42}) {43'use server'44await requireUser();4546// suppression en base47await prisma.historyItem.delete({48where: { id: historyItemId },49});5051revalidatePath('/history') // ^? force la revalidation côté serveur52}
Tip
requireUser() vérifie la session et lève une erreur si l’utilisateur n’est
pas connecté. Utile pour sécuriser tes Server Actions.
Composants client : add & remove
On va créer deux boutons interactifs qui appellent nos Server Actions depuis le client.
1'use client';2import { addHistoryItem } from '../server/history';34export function AddHistoryItemButton({5itemId,6type,7}: {8itemId: number;9type: 'movie' | 'serie';10}) {11return (12<button13onClick={() => addHistoryItem({ itemId, type })}14className="btn-add"15>16➕ Ajouter à l’historique17</button>18);19}
1'use client';2import { useTransition } from 'react';3import { removeHistoryItem } from '../server/history';45export function RemoveHistoryItemButton({6historyItemId,7removeOptimistic,8}: {9historyItemId: number;10removeOptimistic: (id: number) => void;11}) {12const [isPending, startTransition] = useTransition();1314return (15<button16disabled={isPending}17onClick={() => {18startTransition(() => {19// suppression optimiste20removeOptimistic(historyItemId) // ^? mise à jour UI en local21// appel réel22removeHistoryItem({ historyItemId })23});24}}25className="btn-remove"26>27🗑 Supprimer28</button>29);30}
Attention
On fait la suppression optimiste avant l’appel serveur. Ainsi l’UI répond immédiatement, même en cas de latence.
La liste d’historique et UI optimiste
Le composant HistoryList affiche l’historique, groupé par date, et gère la suppression optimiste :
1'use client';2import { Suspense, useOptimistic } from 'react';3import { HistoryItem } from '../server/history';4import { HistoryMediaItem } from './HistoryMediaItem';56export function HistoryList({7initialItems,8}: {9initialItems: HistoryItem[];10}) {11// optimiste : on stocke en local et on filtre12const [items, removeOptimistic] = useOptimistic<13HistoryItem[],14number15>(initialItems, (list, id) => list.filter(i => i.id !== id));1617return (18<Suspense fallback={<div>Chargement...</div>}>19{items.length === 0 ? (20<p>Aucun contenu dans ton historique.</p>21) : (22items.map(item => (23<HistoryMediaItem24key={item.id}25item={item}26removeOptimistic={removeOptimistic}27/>28))29)}30</Suspense>31);32}
Chaque HistoryMediaItem délègue à MovieCard ou SerieCard :
1import { MovieCard } from '../components/MovieCard';2import { SerieCard } from '../components/MovieCard';34export function HistoryMediaItem({5item,6removeOptimistic,7}: {8item: HistoryItem;9removeOptimistic: (id: number) => void;10}) {11if (item.movieId) {12return (13<MovieCard14action="remove"15historyItemId={item.id}16removeHistoryItemOptimisticly={removeOptimistic}17movieId={item.movieId}18/>19);20}21if (item.serieId) {22return (23<SerieCard24action="remove"25historyItemId={item.id}26removeHistoryItemOptimisticly={removeOptimistic}27serieId={item.serieId}28/>29);30}31return null;32}
Tip
useOptimistic prend la liste initiale et une fonction qui décrit
comment la mettre à jour en local (ici, on enlève l’élément supprimé).
Révalidation forcée avec revalidatePath
Après la suppression, on veut s’assurer que le cache du RSC est mis à jour :
1export async function removeHistoryItem({ historyItemId }) {2'use server';3// ...4await prisma.historyItem.delete({ where: { id: historyItemId } });5revalidatePath('/history') // ^? invalide la route history côté cache6}
Cela déclenche le rechargement du data-fetching pour la page /history.
En résumé
- On a défini un modèle
HistoryItemet marché la migration - On a exposé 3 Server Actions :
addHistoryItem,getHistory,removeHistoryItem - On a construit deux boutons React Client pour appeler ces actions
- On a implémenté une liste avec
useOptimisticpour une UI réactive - On a forcé la revalidation du cache avec
revalidatePath
Tu maîtrises maintenant l’ajout/suppression dans l’historique avec une UX fluide et sécurisée !
Exercices
-
Ajouter un Loader
Ajoute un état visuel (spinner ou skeleton) sur le bouton d’ajout tant que l’actionaddHistoryItemest en cours. -
Regrouper par mois
ModifieHistoryListpour afficher les items groupés non pas par jour, mais par mois (ex. “Janvier 2024”). -
Annulation optimiste
Implémente un bouton “Annuler” qui ré-insère un élément supprimé si la suppression échoue (tu peux simuler une erreur côté serveur).