Contact: correction BarreRecherche (composant a props, hooks dans le composant, fragment) + Badge (passer Type App->Carte->Badge) + enonce React #3 (formulaire, tri, React Router)
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# 🔧 Correction — `BarreRecherche.jsx` (+ le Badge)
|
||||
|
||||
Super boulot : ton app **marche** (likes par carte, total qui monte, données chargées via `fetch`, persistance `localStorage`). 👏 Il te reste à **bien découper** (Module H) et à réparer le Badge. On y va.
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Pourquoi `BarreRecherche.jsx` casse tout
|
||||
|
||||
Ton fichier actuel :
|
||||
```jsx
|
||||
const [jeux, setJeux] = useState([]); // ❌ hook AU NIVEAU DU FICHIER
|
||||
const [recherche, setRecherche] = useState(""); // ❌ idem
|
||||
useEffect(() => { ... }, [jeux]); // ❌ idem
|
||||
|
||||
function BarreRecherche() {
|
||||
return (
|
||||
<input ... />
|
||||
{filtres.length === 0 && <p>...</p>} // ❌ plusieurs éléments sans <> </>
|
||||
{filtres.map(...)}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Trois erreurs :
|
||||
1. **Les hooks (`useState`, `useEffect`) doivent être DANS le composant**, pas au niveau du fichier. Hors d'une fonction composant, ils n'ont aucun sens → erreur.
|
||||
2. **Un composant doit renvoyer UN seul élément.** Là, tu renvoies un `<input>` **et** des `<p>`/`<Carte>` côte à côte → erreur JSX. (On enveloppe avec `<> … </>` quand il y en a plusieurs.)
|
||||
3. Tu essaies de remettre **tout** (état, liste, total) dans `BarreRecherche` — alors que ça marche déjà très bien dans `App`.
|
||||
|
||||
> 🧠 **L'erreur de raisonnement :** tu veux « sortir » du code dans un composant, mais tu emmènes l'**état** avec. Or l'état doit **rester dans `App`** (c'est lui qui orchestre). Un petit composant comme `BarreRecherche` doit être **« bête »** : il reçoit ce dont il a besoin en **props**, et ne décide de rien.
|
||||
|
||||
---
|
||||
|
||||
## ✅ La bonne version : un composant qui reçoit des **props**
|
||||
|
||||
`BarreRecherche` n'a besoin que de **deux choses** : la valeur à afficher, et quoi faire quand on tape. On les lui passe en props.
|
||||
|
||||
### `BarreRecherche.jsx`
|
||||
```jsx
|
||||
function BarreRecherche({ valeur, onChange }) {
|
||||
return (
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search Game…"
|
||||
value={valeur}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default BarreRecherche;
|
||||
```
|
||||
> 🔎 Pas de `useState` ici, pas de liste, pas de total. Juste un input qui **affiche** `valeur` et **remonte** les frappes via `onChange`. C'est tout son travail.
|
||||
|
||||
### Dans `App.jsx` : l'état RESTE ici, on passe les props
|
||||
```jsx
|
||||
import BarreRecherche from "./components/BarreRecherche";
|
||||
|
||||
// ... dans le return, à la place de ton <input> :
|
||||
<BarreRecherche
|
||||
valeur={recherche}
|
||||
onChange={(e) => setRecherche(e.target.value)}
|
||||
/>
|
||||
```
|
||||
|
||||
> 🧩 **À retenir :** l'état (`recherche`, `total`, `jeux`…) vit dans `App`. Les petits composants reçoivent des **props** (des données + des fonctions) et restent **simples et réutilisables**. C'est exactement la même idée que `Carte` qui reçoit `onLike`.
|
||||
|
||||
**✅ Test :** la recherche fonctionne **exactement comme avant**, mais l'`<input>` est maintenant dans son propre composant propre.
|
||||
|
||||
> 💡 Tu peux faire pareil pour la **liste** : un composant `Liste` qui reçoit le tableau filtré et fait le `.map()`. `App` ne ferait plus qu'**assembler** `<BarreRecherche/>`, `<Liste/>`, `<Footer/>`.
|
||||
|
||||
---
|
||||
|
||||
## 🎖️ Le Badge qui s'affiche partout (« Jeu 3D » sur tout)
|
||||
|
||||
Ton `Badge` attend une prop `Type` :
|
||||
```jsx
|
||||
function Badge({ Type }) {
|
||||
if (Type === "2d") return null;
|
||||
return <span className="badge"> Jeu 3D </span>;
|
||||
}
|
||||
```
|
||||
Mais dans `Carte` tu l'appelles **sans rien** : `<Badge />`. Donc `Type` vaut `undefined` → ce n'est pas `"2d"` → il affiche « Jeu 3D » **sur toutes les cartes**, même les jeux 2D.
|
||||
|
||||
### La correction : faire **descendre** `Type` jusqu'au Badge
|
||||
Il faut passer `Type` de `App` → `Carte` → `Badge`.
|
||||
|
||||
1. Dans `App.jsx`, ajoute la prop à la carte :
|
||||
```jsx
|
||||
<Carte
|
||||
key={jeu.id}
|
||||
gameId={jeu.id}
|
||||
titre={jeu.titre}
|
||||
console={jeu.console}
|
||||
annee={jeu.annee}
|
||||
Type={jeu.Type} // 👈 on transmet le type
|
||||
onLike={() => setTotal((t) => t + 1)}
|
||||
/>
|
||||
```
|
||||
2. Dans `Carte.jsx`, passe-le au Badge :
|
||||
```jsx
|
||||
<Badge Type={props.Type} />
|
||||
```
|
||||
|
||||
**✅ Test :** seuls les jeux **3D** affichent le badge ; les 2D (comme *Oracle of Ages*) n'ont rien.
|
||||
|
||||
> 🧩 **À retenir :** une prop ne « saute » pas les étages. Pour qu'un petit composant tout en bas (`Badge`) connaisse une donnée, il faut la lui **transmettre de parent en enfant** (`App` → `Carte` → `Badge`).
|
||||
|
||||
---
|
||||
|
||||
## ✅ Récap
|
||||
- [ ] `BarreRecherche` = composant simple qui reçoit `valeur` + `onChange` en props (pas de hook dedans)
|
||||
- [ ] L'état reste dans `App` ; on passe les props
|
||||
- [ ] (Bonus) extraire aussi un composant `Liste`
|
||||
- [ ] Passer `Type` jusqu'au `Badge` (`App` → `Carte` → `Badge`)
|
||||
|
||||
Quand c'est bon, tu peux enchaîner sur **`ENONCE_react_3_contact.md`** (la suite). 🚀
|
||||
@@ -0,0 +1,155 @@
|
||||
# 🗡️ Énoncé React #3 — formulaire, tri & plusieurs pages
|
||||
|
||||
(À attaquer une fois `BarreRecherche` et le Badge réparés via `CORRECTION_barre_recherche.md`.)
|
||||
|
||||
Tu maîtrises déjà : composants, props, état, **lifting state up**, `useEffect`, **`fetch`**. On monte d'un cran avec **3 nouveaux gros morceaux**. Explications + mini-exemples ; à toi d'appliquer sur tes jeux Zelda.
|
||||
|
||||
| Module | Tu apprends | Niveau |
|
||||
|---|---|---|
|
||||
| I | **Formulaire d'ajout** : modifier une liste en state | ⭐⭐ |
|
||||
| J | **Trier** la liste (select contrôlé + `sort`) | ⭐⭐ |
|
||||
| K | **Plusieurs pages** avec React Router | ⭐⭐⭐ |
|
||||
|
||||
---
|
||||
|
||||
## 📝 Module I — Ajouter un jeu via un formulaire
|
||||
|
||||
Ta liste `jeux` est **déjà** un état (tu l'as remplie avec `fetch`), parfait : on peut donc y **ajouter**.
|
||||
|
||||
### Un formulaire contrôlé
|
||||
```jsx
|
||||
const [titre, setTitre] = useState("");
|
||||
const [annee, setAnnee] = useState("");
|
||||
|
||||
function ajouter(e) {
|
||||
e.preventDefault(); // empêche le rechargement de page
|
||||
if (titre.trim() === "") return; // on n'ajoute pas du vide
|
||||
const nouveau = { id: Date.now(), titre, annee: Number(annee), console: "?", Type: "3d" };
|
||||
setJeux([...jeux, nouveau]); // 👈 NOUVEAU tableau = ancien + le nouveau
|
||||
setTitre(""); // on vide les champs
|
||||
setAnnee("");
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={ajouter}>
|
||||
<input value={titre} onChange={(e) => setTitre(e.target.value)} placeholder="Titre du jeu" />
|
||||
<input value={annee} onChange={(e) => setAnnee(e.target.value)} placeholder="Année" />
|
||||
<button type="submit">Ajouter</button>
|
||||
</form>
|
||||
);
|
||||
```
|
||||
|
||||
> 🧩 **Le point clé :** on ne « pousse » pas dans le tableau existant (`jeux.push(...)` ❌). On crée un **nouveau tableau** avec `[...jeux, nouveau]` et on le donne à `setJeux`. React ne réaffiche que si on lui passe une **nouvelle** valeur. C'est l'**immutabilité** : on remplace, on ne modifie pas sur place.
|
||||
|
||||
**✅ Test :** remplis le formulaire, clique « Ajouter » → la carte apparaît, les champs se vident, la page ne recharge pas.
|
||||
|
||||
> 🚀 Bonus : un bouton « 🗑️ Supprimer » par carte → `setJeux(jeux.filter((j) => j.id !== id))` (un nouveau tableau **sans** celui-là).
|
||||
|
||||
---
|
||||
|
||||
## 🔀 Module J — Trier la liste
|
||||
|
||||
Un `<select>` **contrôlé** pour choisir le tri :
|
||||
```jsx
|
||||
const [tri, setTri] = useState("titre");
|
||||
|
||||
const affiches = [...filtres].sort((a, b) => {
|
||||
if (tri === "titre") return a.titre.localeCompare(b.titre);
|
||||
if (tri === "annee") return a.annee - b.annee;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<select value={tri} onChange={(e) => setTri(e.target.value)}>
|
||||
<option value="titre">Titre (A→Z)</option>
|
||||
<option value="annee">Année</option>
|
||||
</select>
|
||||
{affiches.map((jeu) => ( /* ... ta Carte ... */ ))}
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
> 🔎 `[...filtres]` = une **copie** (pour ne pas modifier l'original avec `sort`). L'ordre logique : on **filtre** (recherche) → on **trie** → on `.map()`.
|
||||
|
||||
**✅ Test :** change le tri → l'ordre des cartes change.
|
||||
|
||||
---
|
||||
|
||||
## 🧭 Module K — Plusieurs pages avec React Router
|
||||
|
||||
Jusqu'ici, **une seule page**. Pour une page **détail** par jeu (sa console, son année, ses likes…), on utilise **React Router**.
|
||||
|
||||
### K.1 — Installer
|
||||
Dans le terminal, dans le dossier du projet :
|
||||
```bash
|
||||
npm install react-router-dom
|
||||
```
|
||||
|
||||
### K.2 — Envelopper l'app (dans `main.jsx`)
|
||||
```jsx
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
createRoot(document.getElementById("root")).render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
);
|
||||
```
|
||||
|
||||
### K.3 — Définir les routes (dans `App.jsx`)
|
||||
```jsx
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Accueil />} />
|
||||
<Route path="/jeu/:id" element={<Detail />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
```
|
||||
- `path="/"` → ta page d'accueil (la liste).
|
||||
- `path="/jeu/:id"` → page **dynamique** : `:id` change selon le jeu.
|
||||
|
||||
### K.4 — Des liens (au lieu de `<a href>`)
|
||||
Dans ta `Carte` :
|
||||
```jsx
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
<Link to={`/jeu/${props.gameId}`}>Voir les détails</Link>
|
||||
```
|
||||
> 🔎 `<Link>` navigue **sans recharger** la page. `<a href>` rechargerait tout.
|
||||
|
||||
### K.5 — Lire l'id dans la page détail
|
||||
```jsx
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
function Detail() {
|
||||
const { id } = useParams(); // récupère le :id de l'URL
|
||||
// retrouve le bon jeu par son id, et affiche-le
|
||||
return <h1>Détails du jeu {id}</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Test :** clique « Voir les détails » → l'URL devient `/jeu/2`, la page détail s'affiche **sans rechargement**. Le bouton « précédent » du navigateur ramène à la liste.
|
||||
|
||||
> 🧩 **Les 4 briques de React Router :** `<BrowserRouter>` (enveloppe), `<Routes>/<Route>` (quelle page pour quelle URL), `<Link>` (naviguer), `useParams()` (lire un morceau dynamique de l'URL). C'est la base de **tous** les sites multi-pages en React.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist
|
||||
- [ ] I — formulaire d'ajout (immutabilité avec `[...jeux, ...]`)
|
||||
- [ ] J — tri via un `<select>` contrôlé
|
||||
- [ ] K — React Router : accueil + page détail (`Link`, `useParams`)
|
||||
|
||||
## 🚀 Encore plus loin
|
||||
- Bouton **supprimer** (`filter`) et **éditer** un jeu.
|
||||
- Un **custom hook** `useLocalStorage` pour ranger la logique de persistance des likes.
|
||||
- Une vraie **mise en page** CSS de la page détail (jaquette du jeu, etc.).
|
||||
|
||||
## 🧠 Nouveaux concepts
|
||||
Liste en état + **immutabilité** (`[...]`, `filter`) · formulaire contrôlé + `onSubmit`/`preventDefault` · tri (`select` contrôlé + `sort`) · **React Router** (`BrowserRouter`, `Routes`, `Route`, `Link`, `useParams`).
|
||||
|
||||
Bloquée ? Note ce que tu as tapé + le message exact (console F12). On débloque.
|
||||
Reference in New Issue
Block a user