# 🎼 ÉnoncĂ© React #2 — ton site Geometry Dash (version longue) Bien jouĂ© Guillaume : tu as un Header avec ton intro, un Footer, un composant `Carte` avec des **props**, et un `Compteur` avec `useState`. Les briques sont lĂ . 👌 Le souci : tes cartes affichent encore le contenu du tuto (`Suivi en temps rĂ©el`
), tu les Ă©cris **une par une Ă  la main**, et ton compteur dit encore « VĂ©hicules ». On va en faire un **vrai site sur tes trilogies**, et surtout apprendre **beaucoup de React** au passage. > 🎯 Ce document est une **sĂ©rie de modules** (A → H), du plus simple au plus costaud. Fais-les **dans l'ordre**, teste Ă  chaque fois. Je t'explique les **mĂ©caniques** et te donne des mini-exemples ; Ă  toi d'**appliquer sur ton thĂšme** (je ne donne pas le site fini). Compte plusieurs sĂ©ances — c'est normal. | Module | Tu apprends | Niveau | |---|---|---| | A | Listes : `.map()` + `key` | ⭐ | | B | Affichage conditionnel + extraire un `` | ⭐ | | C | État local par carte (`useState`) | ⭐ | | D | Champ **contrĂŽlĂ©** + recherche/filtre + Ă©tat vide | ⭐⭐ | | E | **Faire remonter l'Ă©tat** (lifting state up) + callbacks en props | ⭐⭐ | | F | **`useEffect`** : titre de page + persistance `localStorage` | ⭐⭐ | | G | **Charger les donnĂ©es avec `fetch`** (dans React) | ⭐⭐⭐ | | H | Re-dĂ©coupage propre (``, ``) | ⭐⭐ | --- ## đŸ§± Module A — Afficher une liste : `.map()` + `key` Aujourd'hui tu Ă©cris chaque carte Ă  la main. La bonne mĂ©thode : **un tableau de donnĂ©es** + **`.map()`**. ### A.1 — un tableau (au-dessus de `function App()`) ```jsx const trilogies = [ { id: 1, nom: "Bloodbath", createur: "Riot", difficulte: "Extreme Demon" }, { id: 2, nom: "Cataclysm", createur: "Ggb0y", difficulte: "Extreme Demon" }, { id: 3, nom: "Sonic Wave", createur: "Cyclic", difficulte: "Extreme Demon" }, // ... tes 5 trilogies ]; ``` ### A.2 — gĂ©nĂ©rer les cartes ```jsx function App() { return (
{trilogies.map((trilo) => ( ))}
); } ``` > đŸ§© **Deux nouveautĂ©s :** > - **`{ ... }`** dans le JSX = « insĂšre ici du JavaScript ». `{trilogies.map(...)}` insĂšre **une carte par Ă©lĂ©ment**. > - **`key={...}`** = un identifiant **unique** par Ă©lĂ©ment (utilise `id`, pas l'index). React en a besoin pour suivre chaque carte ; sans lui → **warning** dans la console. **✅ Test :** ajoute une trilogie au tableau → une carte apparaĂźt toute seule. --- ## đŸŽ–ïž Module B — Affichage conditionnel + un composant `` D'abord enrichis `Carte` : ```jsx function Carte(props) { return (

{props.nom}

Par {props.createur}

Difficulté : {props.difficulte}

); } ``` **Affichage conditionnel** — afficher un truc **seulement si** une condition est vraie : ```jsx {props.difficulte === "Extreme Demon" && đŸ”„ Extreme} ``` - `condition && <...>` = « si vrai, affiche, sinon rien ». - choix entre deux : `{props.fini ? "✅ battu" : "❌ pas battu"}`. > 💡 Dans le JSX on n'Ă©crit pas de `if` Ă  l'intĂ©rieur du `return` : on utilise `&&` (afficher ou rien) ou `? :` (A ou B). **Exercice bonus — extraire un composant ``** (1 composant = 1 fichier) : ```jsx // components/Badge.jsx function Badge({ difficulte }) { if (difficulte !== "Extreme Demon") return null; // un composant peut renvoyer "rien" return đŸ”„ Extreme; } export default Badge; ``` > 🔎 Nouveau : `function Badge({ difficulte })` — on « dĂ©balle » directement la prop entre accolades au lieu d'Ă©crire `props.difficulte` partout. Et `return null` = « n'affiche rien ». Deux outils trĂšs courants. **✅ Test :** seules tes trilogies « Extreme Demon » montrent le badge. --- ## ❀ Module C — État local : chaque `Carte` a son `useState` Mets un bouton « j'aime » **dans** `Carte` : ```jsx import { useState } from "react"; function Carte(props) { const [likes, setLikes] = useState(0); return (

{props.nom}

); } ``` > đŸ§© Chaque `` est une **instance indĂ©pendante** avec son **propre Ă©tat** : liker une carte ne touche pas les autres. **✅ Test :** le ❀ d'une carte monte sans bouger les autres. --- ## 🔎 Module D — Champ **contrĂŽlĂ©** + recherche Nouveau concept important : un champ dont la **valeur est pilotĂ©e par un `useState`** (on dit « contrĂŽlĂ© »). ```jsx function App() { const [recherche, setRecherche] = useState(""); const filtrees = trilogies.filter((t) => t.nom.toLowerCase().includes(recherche.toLowerCase()) ); return (
setRecherche(e.target.value)} // toute frappe MET À JOUR l'Ă©tat /> {filtrees.length === 0 &&

Aucune trilogie trouvĂ©e 😕

} {filtrees.map((trilo) => ( ))}
); } ``` > đŸ§© **Le champ contrĂŽlĂ©**, c'est la boucle : l'Ă©tat → s'affiche dans le champ (`value`) ; une frappe → met Ă  jour l'Ă©tat (`onChange`) ; l'Ă©tat change → React rĂ©affiche, donc la liste **filtrĂ©e** se recalcule. Tu reconnais ta recherche en JS pur, version React (et React refait l'affichage tout seul, plus de `innerHTML`). > ⚠ DĂ©tail : on filtre **avant** le `.map()`, et on prĂ©voit le cas **« aucun rĂ©sultat »** (l'Ă©tat vide). Un bon rĂ©flexe. **✅ Test :** tape « blood » → seules les trilogies correspondantes restent ; efface → tout revient ; tape n'importe quoi → message « aucune trilogie ». --- ## âŹ†ïž Module E — Faire remonter l'Ă©tat (lifting state up) ProblĂšme : et si tu veux un **total de likes** affichĂ© en haut, qui additionne **toutes** les cartes ? Chaque carte a son like **dans son coin**, App ne les « voit » pas. La solution React : **remonter l'Ă©tat dans le parent** (App) et le **redescendre** aux enfants. L'enfant prĂ©vient le parent via une **fonction passĂ©e en prop** (un *callback*). ```jsx function App() { const [total, setTotal] = useState(0); return (

👍 Total de likes : {total}

{trilogies.map((trilo) => ( setTotal((t) => t + 1)} // 👈 on passe une FONCTION en prop /> ))}
); } function Carte(props) { return (

{props.nom}

{/* l'enfant appelle le callback du parent */}
); } ``` > đŸ§© **L'idĂ©e clĂ© de React :** l'Ă©tat « vit » **au plus haut niveau qui en a besoin**. Les donnĂ©es descendent en **props** ; les Ă©vĂ©nements remontent via des **fonctions en props**. Ici, chaque carte ne sait pas compter le total — elle dit juste « on m'a likĂ©e » au parent, qui, lui, tient le total. > 🔎 `setTotal((t) => t + 1)` (avec une fonction) est la façon **sĂ»re** d'incrĂ©menter quand plusieurs mises Ă  jour peuvent s'enchaĂźner. Prends l'habitude. **✅ Test :** liker n'importe quelle carte fait monter le **total global** en haut. --- ## 🔁 Module F — `useEffect` : agir « Ă  cĂŽtĂ© » de l'affichage `useEffect` sert Ă  exĂ©cuter du code **aprĂšs l'affichage** : changer le titre de l'onglet, lire/Ă©crire dans `localStorage`, lancer une requĂȘte
 Des choses qui ne sont pas « du JSX ». ### F.1 — le titre de l'onglet ```jsx import { useEffect } from "react"; useEffect(() => { document.title = `${trilogies.length} trilogies cultes`; }, []); // 👈 tableau vide = "une seule fois, au chargement" ``` ### F.2 — des likes qui **survivent au rechargement** (`localStorage` + `useEffect`) Dans `Carte` : ```jsx function Carte(props) { // valeur de dĂ©part : ce qui est stockĂ©, sinon 0 const [likes, setLikes] = useState(() => Number(localStorage.getItem("likes-" + props.id)) || 0); // Ă  chaque fois que "likes" change, on le sauvegarde useEffect(() => { localStorage.setItem("likes-" + props.id, likes); }, [likes]); // 👈 "rejoue cet effet quand likes change" return ; } ``` > đŸ§© **À comprendre :** le **tableau de dĂ©pendances** (`[]` ou `[likes]`) dit **quand** rejouer l'effet : `[]` = une seule fois ; `[likes]` = Ă  chaque changement de `likes`. C'est le cƓur de `useEffect`. > Et tu retrouves ton `localStorage` (texte → `Number(...)` pour reconvertir, comme `parseInt` avant). **✅ Test :** like une carte, **recharge la page (F5)** → le nombre est toujours lĂ . Et l'onglet affiche ton titre. --- ## 🌐 Module G — Charger les donnĂ©es avec `fetch` (le vrai truc des apps) Au lieu d'Ă©crire `trilogies` en dur, charge-les depuis un fichier JSON — comme une vraie app va chercher ses donnĂ©es sur un serveur. 1. CrĂ©e `public/trilogies.json` (le dossier `public/` est servi Ă  la racine) avec ton tableau de trilogies (format JSON : guillemets doubles partout). 2. Dans `App` : ```jsx import { useState, useEffect } from "react"; function App() { const [trilogies, setTrilogies] = useState([]); // au dĂ©part : liste vide const [chargement, setChargement] = useState(true); useEffect(() => { fetch("/trilogies.json") .then((r) => r.json()) .then((data) => { setTrilogies(data); setChargement(false); }); }, []); // une seule fois, au chargement if (chargement) return

Chargement


; // tant que les données ne sont pas là return (
{trilogies.map((t) => )}
); } ``` > đŸ§© **Le schĂ©ma Ă  retenir :** un `useState` vide pour accueillir les donnĂ©es → un `useEffect([])` qui `fetch` au chargement → `setTrilogies(data)` remplit l'Ă©tat → React rĂ©affiche avec les donnĂ©es. Et on gĂšre l'Ă©tat **« en cours de chargement »** (trĂšs pro). > > ⚠ Comme en JS pur : `fetch` a besoin du serveur de dev (`npm run dev` te le donne dĂ©jĂ ). Le `public/` est servi Ă  la racine, donc `/trilogies.json`. **✅ Test :** tes cartes s'affichent Ă  partir du JSON. Ajoute une trilogie **dans le fichier** → elle apparaĂźt. --- ## đŸ§č Module H — Re-dĂ©couper proprement Ton `App` commence Ă  beaucoup faire (recherche + total + liste
). Garde-le **assembleur** : extrais des composants, **un par fichier**. - `components/BarreRecherche.jsx` — l'input contrĂŽlĂ© (reçoit `valeur` et `onChange` en props). - `components/Liste.jsx` — reçoit le tableau filtrĂ© et fait le `.map()`. - `components/Badge.jsx` — dĂ©jĂ  fait au module B. > 🧭 La rĂšgle d'or n'a pas changĂ© : **1 composant = 1 fichier**, `App` assemble. DĂšs qu'un fichier « fait trop de choses », dĂ©coupe. --- ## ✅ Checklist - [ ] A — tableau + `.map()` + `key` - [ ] B — affichage conditionnel + composant `` - [ ] C — like local par carte - [ ] D — recherche avec champ **contrĂŽlĂ©** + message « aucun rĂ©sultat » - [ ] E — **total de likes** remontĂ© dans `App` (callback en prop) - [ ] F — `useEffect` : titre d'onglet + likes persistĂ©s en `localStorage` - [ ] G — donnĂ©es chargĂ©es via `fetch` depuis `public/trilogies.json` - [ ] H — re-dĂ©coupage (``, ``) ## 🚀 Encore plus loin - Un **tri** (par difficultĂ©, par likes) avec un `