From b30bae07c24a0af579d210d86b5cf07e898fc524 Mon Sep 17 00:00:00 2001 From: matthieureynier Date: Wed, 24 Jun 2026 09:21:10 +0200 Subject: [PATCH] Enonce React #2 services (Geometry Dash): map/key, conditionnel, etat local, champ controle, lifting state up, useEffect, fetch en React, re-decoupage --- ENONCE_react_services.md | 307 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 ENONCE_react_services.md diff --git a/ENONCE_react_services.md b/ENONCE_react_services.md new file mode 100644 index 0000000..92b1fe0 --- /dev/null +++ b/ENONCE_react_services.md @@ -0,0 +1,307 @@ +# 🎼 É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 `