diff --git a/CORRECTION_page_detail.md b/CORRECTION_page_detail.md new file mode 100644 index 0000000..24adf79 --- /dev/null +++ b/CORRECTION_page_detail.md @@ -0,0 +1,92 @@ +# 🔎 Correction — faire (enfin) vivre la page Détail + +Salut ! 👋 Ton routeur est **nickel** : clic sur « Voir les détails » → l'URL devient `/jeu/2` → la page change **sans recharger**. C'est tout l'intérêt de React Router, bravo. 🎉 + +Sauf que ta page `Detail.jsx` ressemble encore à ça : + +```jsx +function Detail() { + const { id } = useParams(); + return

Détails du jeu {id}

; +} +``` + +Elle affiche juste le **numéro**. On veut le **vrai jeu** : son titre, sa console, son année, son type. Et là, une question piège… + +--- + +## 🧠 Le vrai problème : « où vivent les données ? » + +`Detail` reçoit `id` (genre `"2"`). Mais la **liste des jeux** vit dans le `useState` de… `Accueil` (tu l'as remplie avec `fetch`). 😬 + +`Detail` et `Accueil` sont **deux pages sœurs** : ni l'une dans l'autre. Donc `Detail` **ne peut pas** lire l'état de `Accueil`. C'est le mur classique de React Router. + +> 💡 **La règle :** un composant ne voit que **ses props** et **son propre state**. Pour qu'une donnée soit partagée par deux pages, elle doit vivre **au-dessus des deux**, ou dans un endroit que les deux savent lire. + +Comme ton appli lit déjà ses données via `fetch`, on a une solution simple **et** une solution pro. Prends la simple d'abord. 👇 + +--- + +## ✅ Solution simple — la page détail va chercher la donnée elle-même + +`Detail` connaît l'`id` voulu. Elle peut **refaire le `fetch`** du même fichier et **retrouver** le bon jeu avec `.find()`. + +```jsx +import { useParams, Link } from "react-router-dom"; +import { useState, useEffect } from "react"; + +function Detail() { + const { id } = useParams(); + const [jeu, setJeu] = useState(null); + + useEffect(() => { + fetch("/zelda.json") + .then((r) => r.json()) + .then((data) => { + // ⚠️ piège : useParams renvoie une CHAÎNE. Tes id du JSON sont aussi + // des chaînes ("1","2"...) → ici on compare string === string, ok. + // Mais reste vigilant : si un id est un nombre, fais String(j.id) === id + const trouve = data.find((j) => j.id === id); + setJeu(trouve); + }); + }, [id]); // 👈 on relance si l'id change (navigation d'un détail à un autre) + + if (!jeu) return

Jeu introuvable 🤷 ← retour

; + + return ( +
+ ← Retour à la liste +

{jeu.titre}

+

🕹️ Console : {jeu.console}

+

📅 Année : {jeu.annee}

+

🎮 Type : {jeu.Type}

+
+ ); +} + +export default Detail; +``` + +> 🐛 **Le piège classique ici, c'est le type de l'`id`.** `useParams()` renvoie **toujours une chaîne** (`"2"`). Tes id dans `zelda.json` sont aussi des chaînes → `j.id === id` marche. Mais le jour où un id est un **nombre**, `2 === "2"` vaut `false` et `find` ne trouve rien → compare avec `String(j.id) === id`. + +> 🔎 **Pourquoi `[id]` dans le `useEffect` ?** Sans lui, le fetch ne se ferait **qu'une fois**. Comme on peut naviguer d'un détail à un autre (`/jeu/2` → `/jeu/3`) **sans démonter le composant**, il faut redéclencher l'effet quand `id` change. + +**✅ Test :** clique « détails » sur Breath of the Wild → tu vois Switch / 2017 / 3d, pas juste « 2 ». Le lien « ← Retour » te ramène à la liste. + +--- + +## ⚠️ Les limites de cette solution (et c'est voulu) + +1. Les jeux **ajoutés au formulaire** ne sont **pas** dans `zelda.json` → leur page détail dira « introuvable ». +2. Le **nombre de likes** (stocké dans `localStorage` par chaque `Carte`) n'apparaît pas non plus. + +Pour que **tout** suive jusqu'au détail (jeux ajoutés + likes), il faut **remonter l'état au niveau du routeur** (le `fetch` et la liste vivent dans `App`, pas dans `Accueil`). C'est le **Module L** de l'énoncé final 👇 — le vrai « boss ». 😈 + +--- + +## 🧹 Pendant qu'on y est, 2 petits nettoyages + +1. `console.log(props.gameId)` dans `Carte.jsx` → on enlève les logs de debug avant de livrer. 🧼 +2. Dans le formulaire d'ajout, tu crées le jeu avec `console: "?"`… mais tu as un champ `plateforme`. Mets plutôt `console: plateforme` pour que la console saisie s'affiche vraiment sur la carte. + +> 💡 **À retenir :** une page ne peut pas piocher dans le state d'une autre page. Soit elle **va chercher** la donnée elle-même (re-`fetch` + `find`), soit on fait **remonter l'état** chez le parent commun. Et `useParams()` renvoie **toujours une chaîne** → attention au type quand tu compares aux ids. diff --git a/ENONCE_final_boss.md b/ENONCE_final_boss.md new file mode 100644 index 0000000..9f7eea1 --- /dev/null +++ b/ENONCE_final_boss.md @@ -0,0 +1,199 @@ +# 🏁 Énoncé FINAL — « Boss de fin » 😈 + +Dernier jour ! 🎉 Tu as déjà tout l'arsenal : composants, props, state, lifting state up, `useEffect`, **`fetch`**, formulaire, tri, filtre, **React Router**, **`localStorage`**. Aujourd'hui on **s'amuse** : 4 modules courts et stylés. 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 **🎲 « Au hasard »** (page d'un jeu aléatoire) | ⭐⭐ | 😄 | +| N | **🏆 Le Hall of Fame** des jeux les plus likés | ⭐⭐ | 🥇 | +| O | **🌗 Thème sombre** qui se souvient de ton choix | ⭐⭐ | 🌙 | + +--- + +## 🔑 Module L — Le boss : centraliser les données dans `App` + +Dans `CORRECTION_page_detail.md` tu as fait une version simple (la page détail re-`fetch` toute seule). Ses limites : les jeux **ajoutés au formulaire** et les **likes** n'arrivent pas dans le détail, parce que chacun vit dans son coin. On corrige ça **proprement**. + +**L'idée :** le `fetch`, la liste `jeux` **et** les likes doivent vivre **au-dessus** des deux pages → dans `App`. `App` les passe ensuite **en props** aux routes. + +### L.1 — Remonter le `fetch` et les états dans `App.jsx` +```jsx +import { useState, useEffect } from "react"; +import { Routes, Route } from "react-router-dom"; +import Header from "./components/Header"; +import Footer from "./components/Footer"; +import Accueil from "./pages/Accueil"; +import Detail from "./pages/Detail"; + +function App() { + const [jeux, setJeux] = useState([]); + const [likes, setLikes] = useState(() => + JSON.parse(localStorage.getItem("likes") || "{}") + ); + + useEffect(() => { + fetch("/zelda.json").then((r) => r.json()).then(setJeux); + }, []); + + useEffect(() => { + localStorage.setItem("likes", JSON.stringify(likes)); + }, [likes]); + + function liker(id) { + setLikes((prev) => ({ ...prev, [id]: (prev[id] || 0) + 1 })); + } + + return ( + <> +
+ + + } /> + + } /> + +