From e798810283d7a16f21201a94bdf4a541dd8a9f8e Mon Sep 17 00:00:00 2001 From: matthieureynier Date: Fri, 26 Jun 2026 10:02:43 +0200 Subject: [PATCH] Enonce final: plus d'exercices (versus, stats, 404, tri inverse, custom hook, edition) + ton allege --- ENONCE_final_boss.md | 307 ++++++++++++++++++++++++++++--------------- 1 file changed, 204 insertions(+), 103 deletions(-) diff --git a/ENONCE_final_boss.md b/ENONCE_final_boss.md index 0d2641b..18aac3d 100644 --- a/ENONCE_final_boss.md +++ b/ENONCE_final_boss.md @@ -1,21 +1,30 @@ -# 🏁 ÉnoncĂ© FINAL — « Boss de fin » 😈 +# 🏁 ÉnoncĂ© FINAL — « Boss de fin » -Dernier jour ! 🎉 Tu as dĂ©jĂ  tout l'arsenal : composants, props, state, lifting state up, `useEffect`, formulaire, tri, filtre, **React Router**. Aujourd'hui on **s'amuse** : 4 modules courts et stylĂ©s, du plus utile au plus fun. Fais-en autant que tu veux, dans l'ordre que tu veux — mais le **Module L** est le vrai boss, attaque-le en premier. đŸ’Ș +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. -| Module | Tu fais | Niveau | Fun | -|---|---|---|---| -| L | La page dĂ©tail **complĂšte** (state remontĂ© au routeur) | ⭐⭐⭐ | 🔑 | -| M | Bouton **đŸŽČ « Je tente ma chance »** (page au hasard) | ⭐⭐ | 😄 | -| N | **🏆 Le Podium** des trilogies les plus likĂ©es | ⭐⭐ | đŸ„‡ | -| O | **🌗 ThĂšme sombre** qui se souvient de ton choix | ⭐⭐ | 🌙 | +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 +## 🔑 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`. Le souci : les trilogies **ajoutĂ©es au formulaire** et les **likes** n'arrivent pas dans la page dĂ©tail. On corrige ça **proprement**. +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`. `App` la passe ensuite **en props** aux deux routes. +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 @@ -31,174 +40,266 @@ function App() { return ( - - } - /> - } - /> + + } /> + + } /> ); } ``` -> đŸ§© **Le dĂ©clic :** une route peut recevoir des props comme n'importe quel composant — `element={
}`. C'est ça qui « branche » la donnĂ©e sur les deux pages. +> 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 (au lieu de son state local) -EnlĂšve les `useState` de `trilogies` et `likes` dans `Accueil` : ils viennent maintenant de `props`. Garde en local ce qui est **propre Ă  la page d'accueil** (`recherche`, `tri`, les champs du formulaire). +### 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 }) { - // recherche, tri, nom, niveaux restent en useState local // 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** +### 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

; + if (!trilogie) return

Introuvable — retour

; return (
← Retour

{trilogie.nom}

-

🎼 Niveaux : {trilogie.niveaux}

+

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 bien « ❀ Likes : 3 ». 🏆 +**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 en React. +> 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 — « Je tente ma chance » +## 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 avec un clic sur un ``). +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"; -function Accueil({ trilogies, /* ... */ }) { - const navigate = useNavigate(); - - function hasard() { - const i = Math.floor(Math.random() * trilogies.length); - navigate(`/trilogie/${trilogies[i].id}`); - } - // ... - return ( - <> - - {/* ... reste de la page ... */} - - ); +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
). Deux outils, deux usages. +> `` = 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. +**Test :** clique plusieurs fois → tu tombes sur des trilogies diffĂ©rentes. --- -## 🏆 Module N — Le Podium +## Module N — Le podium -En haut de l'accueil, affiche le **Top 3** des trilogies les plus likĂ©es, avec des mĂ©dailles. +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); // on ne garde que les 3 premiĂšres + .slice(0, 3); -// dans le JSX : -
-

🏆 Podium

- {podium.map((t, index) => ( -

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

- ))} -
+//
+// {podium.map((t, index) => ( +//

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

+// ))} +//
``` -> 🔎 `.slice(0, 3)` = « garde les Ă©lĂ©ments d'indice 0, 1, 2 ». Et le **2ᔉ paramĂštre** de `.map((t, index) => ...)`, c'est la **position** dans la liste → parfait pour choisir la mĂ©daille. +> `.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 temps rĂ©el. +**Test :** like beaucoup une trilogie → elle grimpe sur le podium en direct. --- -## 🌗 Module O — ThĂšme sombre (qui se souvient) +## Module O — Supprimer une trilogie -Un bouton 🌙 / ☀ qui bascule toute la page en sombre, **et garde ton choix** mĂȘme aprĂšs un refresh (`localStorage`, comme les likes du Module H). +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). -### O.1 — L'Ă©tat + la mĂ©moire ```jsx const [sombre, setSombre] = useState( () => localStorage.getItem("theme") === "sombre" ); - useEffect(() => { localStorage.setItem("theme", sombre ? "sombre" : "clair"); }, [sombre]); -``` -### O.2 — Appliquer une classe sur le conteneur -```jsx -
- - {/* ... */} -
+//
+// ``` - -### O.3 — Le CSS (dans `App.css`) ```css -.sombre { - background-color: #1a1a2e; - color: #eaeaea; -} -.sombre .carte { - background-color: #16213e; - color: #eaeaea; -} +.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`, et le navigateur applique les bonnes rĂšgles. C'est comme ça que marchent **tous** les dark modes que tu connais. +> 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 la page (F5) → c'est **toujours** sombre. 🌙 +**Test :** passe en sombre → recharge (F5) → c'est toujours sombre. --- -## ✅ Checklist du dernier jour -- [ ] L — state remontĂ© dans `App`, page dĂ©tail complĂšte (ajouts + likes inclus) 🔑 -- [ ] M — bouton đŸŽČ avec `useNavigate` -- [ ] N — podium Top 3 (`sort` + `slice` + `index` dans `map`) -- [ ] O — thĂšme sombre persistant (`localStorage` + classe CSS pilotĂ©e par state) +## Module Q — Mode Versus -## 🚀 Si tu finis tout (true endgame) -- 🎉 **Confetti au like** : une petite animation CSS quand le compteur monte. -- ⚔ **Mode Versus** : choisis 2 trilogies dans 2 `` pour choisir deux trilogies ; on compare leurs likes et on affiche la gagnante. -## 🧠 Nouveaux concepts du jour -`useNavigate` (naviguer depuis du code) · Ă©tat remontĂ© **au niveau du routeur** + props passĂ©es Ă  une `` · `slice` + `index` du `map` pour un classement · **CSS pilotĂ© par le state** (`className` conditionnel) · persistance `localStorage`. +```jsx +const [a, setA] = useState(trilogies[0].id); +const [b, setB] = useState(trilogies[1].id); -GG pour ces deux semaines. đŸ«Ą Montre-moi ton boss quand il est battu ! +const likesA = likes[a] || 0; +const likesB = likes[b] || 0; +const gagnante = likesA === likesB ? "ÉgalitĂ©" : + (likesA > likesB ? trilogies.find(t => t.id === Number(a)).nom + : trilogies.find(t => t.id === Number(b)).nom); +``` +Deux `` : elle revient en chaĂźne. + +**Test :** change les deux menus → le verdict se met Ă  jour tout seul. + +--- + +## Module R — Statistiques + +Une petite barre de stats calculĂ©e Ă  partir des donnĂ©es. On muscle `reduce`. + +```jsx +const nbTrilogies = trilogies.length; +const totalLikes = Object.values(likes).reduce((s, n) => s + n, 0); +const moyenne = nbTrilogies ? (totalLikes / nbTrilogies).toFixed(1) : 0; +//

{nbTrilogies} trilogies · {totalLikes} likes · {moyenne} likes/trilogie

+``` + +> `reduce` rĂ©duit un tableau Ă  **une seule** valeur en accumulant. Ici on additionne tous les likes. + +**Test :** ajoute/like des trilogies → les chiffres bougent. + +--- + +## Module S — Page « 404 » + +Que se passe-t-il si on tape `/trilogie/banane` ou une URL qui n'existe pas ? Rien d'affichĂ©. On ajoute une route attrape-tout. + +```jsx +import { Link } from "react-router-dom"; +function NotFound() { + return

404 — page introuvable

Retour Ă  l'accueil
; +} +// dans , en DERNIER : +// } /> +``` + +> `path="*"` = « toutes les URL non reconnues ». À mettre **en dernier**, sinon il attrape tout avant les autres. + +**Test :** tape une URL bidon dans la barre → la page 404 s'affiche. + +--- + +## Module T — Tri ascendant ↔ descendant + +Ajoute un bouton qui inverse l'ordre du tri en cours. + +```jsx +const [ordre, setOrdre] = useState("asc"); +// aprĂšs ton .sort(...) : +const affichees = [...maListeTriee]; +if (ordre === "desc") affichees.reverse(); +// +``` + +**Test :** clique le bouton → l'ordre des cartes s'inverse. + +--- + +## Module U — Custom hook `useLocalStorage` (refacto) + +Tu rĂ©pĂštes le mĂȘme schĂ©ma `useState` + `useEffect(localStorage
)` pour les likes et le thĂšme. On range ça dans **un hook rĂ©utilisable**. + +```jsx +// src/useLocalStorage.js +import { useState, useEffect } from "react"; + +export function useLocalStorage(cle, valeurParDefaut) { + const [valeur, setValeur] = useState(() => { + const stockee = localStorage.getItem(cle); + return stockee !== null ? JSON.parse(stockee) : valeurParDefaut; + }); + useEffect(() => { + localStorage.setItem(cle, JSON.stringify(valeur)); + }, [cle, valeur]); + return [valeur, setValeur]; +} +``` +Puis : `const [likes, setLikes] = useLocalStorage("likes", {});` — et les likes survivent au refresh, gratuitement. + +> Un custom hook, c'est juste une fonction qui commence par `use` et qui utilise d'autres hooks. Ça permet de **rĂ©utiliser de la logique** entre composants. + +**Test :** like des trilogies → recharge → les likes sont toujours lĂ . + +--- + +## Module V — Éditer une trilogie + +Le plus dur. Un bouton « ✏ » passe une carte en mode Ă©dition (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.