# 🏁 ÉnoncĂ© FINAL — « Boss de fin » Dernier jour. Tu as tout l'arsenal : composants, props, state, lifting state up, `useEffect`, formulaire, tri, filtre, React Router. Aujourd'hui, place Ă  la pratique libre. Un seul module est **obligatoire** : le **Module L** (la page dĂ©tail, le vrai boss). Les autres forment un **menu** : pioche dans l'ordre que tu veux, fais-en le plus possible. Chacun est court et indĂ©pendant. | Module | Tu fais | Niveau | |---|---|---| | **L** | Page dĂ©tail **complĂšte** (state remontĂ© au routeur) — *obligatoire* | ⭐⭐⭐ | | M | Bouton « au hasard » (`useNavigate`) | ⭐⭐ | | N | Le podium des trilogies les plus likĂ©es | ⭐⭐ | | O | Bouton « supprimer » une trilogie | ⭐⭐ | | P | ThĂšme sombre qui se souvient du choix | ⭐⭐ | | Q | Mode Versus : deux trilogies s'affrontent | ⭐⭐ | | R | Statistiques (total, moyenne, championne) | ⭐⭐ | | S | Page « 404 » pour les URL inconnues | ⭐⭐ | | T | Tri ascendant ↔ descendant | ⭐ | | U | Custom hook `useLocalStorage` (refacto) | ⭐⭐⭐ | | V | Éditer une trilogie | ⭐⭐⭐ | --- ## 🔑 Module L — Le boss : faire suivre TOUT jusqu'au dĂ©tail (obligatoire) Dans `CORRECTION_page_detail.md` tu as fait une version simple avec `data.js`. Sa limite : les trilogies **ajoutĂ©es au formulaire** et les **likes** n'arrivent pas dans la page dĂ©tail. On corrige ça proprement. L'idĂ©e : la donnĂ©e doit vivre **au-dessus** des deux pages, donc dans `App`, qui la passe ensuite **en props** aux deux routes. ### L.1 — Remonter le state dans `App.jsx` ```jsx import { useState } from "react"; import { Routes, Route } from "react-router-dom"; import { donneesInitiales } from "./data"; import Accueil from "./pages/Accueil"; import Details from "./pages/Details"; function App() { const [trilogies, setTrilogies] = useState(donneesInitiales); const [likes, setLikes] = useState({}); return ( } /> } /> ); } ``` > Le dĂ©clic : une route reçoit des props comme n'importe quel composant — `element={
}`. C'est ça qui « branche » la donnĂ©e sur les deux pages. ### L.2 — `Accueil` lit/Ă©crit via les props EnlĂšve les `useState` de `trilogies` et `likes` dans `Accueil` : ils viennent maintenant des props. Garde en local ce qui est propre Ă  l'accueil (`recherche`, `tri`, champs du formulaire). ```jsx function Accueil({ trilogies, setTrilogies, likes, setLikes }) { // ajouter() fait toujours setTrilogies([...trilogies, nouvelle]) // liker() fait toujours setLikes(prev => ({...prev, [id]: (prev[id]||0)+1})) } ``` ### L.3 — `Details` affiche la vraie trilogie avec ses likes ```jsx function Details({ trilogies, likes }) { const { id } = useParams(); const trilogie = trilogies.find((t) => t.id === Number(id)); if (!trilogie) return

Introuvable — retour

; return (
← Retour

{trilogie.nom}

Niveaux : {trilogie.niveaux}

❀ Likes : {likes[trilogie.id] || 0}

); } ``` **Test du boss :** ajoute une trilogie au formulaire → clique « dĂ©tails » dessus → elle s'affiche (alors qu'avec `data.js` elle Ă©tait introuvable). Like-la 3 fois sur l'accueil → son dĂ©tail affiche « ❀ Likes : 3 ». > Ce que tu viens de faire s'appelle « lifting state up » poussĂ© jusqu'au routeur : l'Ă©tat monte au plus petit ancĂȘtre commun des composants qui en ont besoin. C'est le rĂ©flexe d'archi central en React. --- ## Module M — Bouton « au hasard » Un bouton qui t'envoie sur la page dĂ©tail d'une trilogie au hasard. On dĂ©couvre `useNavigate` (naviguer en JS, pas via un clic sur un ``). ```jsx import { useNavigate } from "react-router-dom"; const navigate = useNavigate(); function hasard() { const i = Math.floor(Math.random() * trilogies.length); navigate(`/trilogie/${trilogies[i].id}`); } // ``` > `` = naviguer au clic sur un lien. `useNavigate()` = naviguer depuis du code (aprĂšs un calcul, un envoi de formulaire
). **Test :** clique plusieurs fois → tu tombes sur des trilogies diffĂ©rentes. --- ## Module N — Le podium En haut de l'accueil, le Top 3 des trilogies les plus likĂ©es, avec des mĂ©dailles. ```jsx const medailles = ["đŸ„‡", "đŸ„ˆ", "đŸ„‰"]; const podium = [...trilogies] .sort((a, b) => (likes[b.id] || 0) - (likes[a.id] || 0)) .slice(0, 3); //
// {podium.map((t, index) => ( //

{medailles[index]} {t.nom} — {likes[t.id] || 0} ❀

// ))} //
``` > `.slice(0, 3)` garde les indices 0, 1, 2. Le 2ᔉ paramĂštre de `.map((t, index) => ...)`, c'est la position dans la liste → parfait pour la mĂ©daille. **Test :** like beaucoup une trilogie → elle grimpe sur le podium en direct. --- ## Module O — Supprimer une trilogie Un bouton « supprimer » sur chaque carte. La clĂ© : l'immutabilitĂ© (un nouveau tableau **sans** l'Ă©lĂ©ment). ```jsx function supprimer(id) { setTrilogies(trilogies.filter((t) => t.id !== id)); } // sur la Carte : // et dans Accueil : onDelete={() => supprimer(trilo.id)} ``` > `filter` renvoie un **nouveau** tableau contenant tout sauf l'Ă©lĂ©ment visĂ©. On ne touche jamais l'ancien (immutabilitĂ©, comme pour l'ajout). **Test :** clique đŸ—‘ïž → la carte disparaĂźt, les autres restent. --- ## Module P — ThĂšme sombre (qui se souvient) Un bouton qui bascule la page en sombre et garde le choix aprĂšs un refresh (`localStorage`, comme tes likes). ```jsx const [sombre, setSombre] = useState( () => localStorage.getItem("theme") === "sombre" ); useEffect(() => { localStorage.setItem("theme", sombre ? "sombre" : "clair"); }, [sombre]); //
// ``` ```css .sombre { background-color: #1a1a2e; color: #eaeaea; } .sombre .carte { background-color: #16213e; color: #eaeaea; } ``` > Tu fais piloter du CSS par du state : `className` change selon `sombre`, le navigateur applique les bonnes rùgles. C'est comme ça que marchent tous les dark modes. **Test :** passe en sombre → recharge (F5) → c'est toujours sombre. --- ## Module Q — Mode Versus Deux `` (un pour `a`, un pour `b`) listant `trilogies.map(t => )`, puis un `

` qui annonce la gagnante. > Pense Ă  `Number(...)` quand tu relis la valeur d'un `` Ă  la place du titre), et « valider » met Ă  jour la liste. Piste : un Ă©tat `enEdition` (l'id en cours d'Ă©dition, ou `null`), et une fonction `modifier(id, nouveauNom)` qui fait ```jsx setTrilogies(trilogies.map((t) => t.id === id ? { ...t, nom: nouveauNom } : t)); ``` > `map` ici renvoie un nouveau tableau oĂč **seule** la trilogie visĂ©e est remplacĂ©e par une copie modifiĂ©e (`{...t, nom}`), les autres inchangĂ©es. Toujours l'immutabilitĂ©. **Test :** Ă©dite un nom → valide → la carte (et sa page dĂ©tail) affichent le nouveau nom. --- ## ✅ Checklist - [ ] L — state remontĂ© dans `App`, page dĂ©tail complĂšte *(obligatoire)* - [ ] M — bouton au hasard · [ ] N — podium · [ ] O — supprimer · [ ] P — thĂšme sombre - [ ] Q — versus · [ ] R — stats · [ ] S — page 404 · [ ] T — tri inversĂ© - [ ] U — custom hook · [ ] V — Ă©dition ## 🧠 Concepts rencontrĂ©s aujourd'hui `useNavigate` · Ă©tat remontĂ© au niveau du routeur + props passĂ©es Ă  une `` · `filter` (supprimer) et `map` (Ă©diter) immuables · `sort`/`slice`/`index` (classement) · `reduce` (stats) · route attrape-tout `path="*"` · CSS pilotĂ© par le state · **custom hook**. Bravo pour ces deux semaines. Montre-moi tes modules battus quand tu en tiens un.