7.5 KiB
🏁 É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. 💪
| 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 | ⭐⭐ | 🌙 |
🔑 Module L — Le boss : faire suivre TOUT jusqu'au détail
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.
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.1 — Remonter le state dans App.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 (
<Routes>
<Route
path="/"
element={
<Accueil
trilogies={trilogies}
setTrilogies={setTrilogies}
likes={likes}
setLikes={setLikes}
/>
}
/>
<Route
path="/trilogie/:id"
element={<Details trilogies={trilogies} likes={likes} />}
/>
</Routes>
);
}
🧩 Le déclic : une route peut recevoir des props comme n'importe quel composant —
element={<Details trilogies={trilogies} />}. 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).
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
function Details({ trilogies, likes }) {
const { id } = useParams();
const trilogie = trilogies.find((t) => t.id === Number(id));
if (!trilogie) return <p>Introuvable 🤷 <Link to="/">← retour</Link></p>;
return (
<div className="detail">
<Link to="/">← Retour</Link>
<h1>{trilogie.nom}</h1>
<p>🎮 Niveaux : {trilogie.niveaux}</p>
<p>❤️ Likes : {likes[trilogie.id] || 0}</p>
</div>
);
}
✅ 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 ». 🏆
🧠 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.
🎲 Module M — « Je tente ma chance »
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 <Link>).
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 (
<>
<button onClick={hasard}>🎲 Je tente ma chance</button>
{/* ... reste de la page ... */}
</>
);
}
🔎
<Link>= naviguer au clic sur un lien.useNavigate()= naviguer depuis du code (après un calcul, un envoi de formulaire…). Deux outils, deux usages.
✅ Test : clique 🎲 plusieurs fois → tu tombes sur des trilogies différentes.
🏆 Module N — Le Podium
En haut de l'accueil, affiche le Top 3 des trilogies les plus likées, avec des médailles.
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
// dans le JSX :
<div className="podium">
<h2>🏆 Podium</h2>
{podium.map((t, index) => (
<p key={t.id}>{medailles[index]} {t.nom} — {likes[t.id] || 0} ❤️</p>
))}
</div>
🔎
.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.
✅ Test : like beaucoup une trilogie → elle grimpe sur le podium en temps réel.
🌗 Module O — Thème sombre (qui se souvient)
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).
O.1 — L'état + la mémoire
const [sombre, setSombre] = useState(
() => localStorage.getItem("theme") === "sombre"
);
useEffect(() => {
localStorage.setItem("theme", sombre ? "sombre" : "clair");
}, [sombre]);
O.2 — Appliquer une classe sur le conteneur
<div className={sombre ? "body sombre" : "body"}>
<button onClick={() => setSombre(!sombre)}>
{sombre ? "☀️ Clair" : "🌙 Sombre"}
</button>
{/* ... */}
</div>
O.3 — Le CSS (dans App.css)
.sombre {
background-color: #1a1a2e;
color: #eaeaea;
}
.sombre .carte {
background-color: #16213e;
color: #eaeaea;
}
🧩 Tu fais piloter du CSS par du state :
classNamechange selonsombre, et le navigateur applique les bonnes règles. C'est comme ça que marchent tous les dark modes que tu connais.
✅ Test : passe en sombre → recharge la page (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+indexdansmap) - O — thème sombre persistant (
localStorage+ classe CSS pilotée par state)
🚀 Si tu finis tout (true endgame)
- 🎉 Confetti au like : une petite animation CSS quand le compteur monte.
- ⚔️ Mode Versus : choisis 2 trilogies dans 2
<select>, compare leurs likes, affiche la gagnante. - 🗑️ Supprimer / éditer une trilogie depuis sa page détail.
- 📱 Soigner le responsive (flex/grid) pour que ce soit beau sur téléphone.
🧠 Nouveaux concepts du jour
useNavigate (naviguer depuis du code) · état remonté au niveau du routeur + props passées à une <Route> · slice + index du map pour un classement · CSS piloté par le state (className conditionnel) · persistance localStorage.
GG pour ces deux semaines. 🫡 Montre-moi ton boss quand il est battu !