Services: correction Carte/likes (1 seule approche: likes dans le parent, total recalcule, fix syntaxe) + enonce React #3 (formulaire d'ajout, tri, React Router)
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# 🔧 Correction — pourquoi ton projet ne s'affiche plus
|
||||
|
||||
Ton commit le dit bien : « + de bugs » 😅. Pas de panique, le diagnostic est simple : tu as **mélangé deux façons** de gérer les likes, et du coup `Carte.jsx` utilise des choses qui n'existent pas chez elle.
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Ce qui cloche dans `Carte.jsx`
|
||||
|
||||
```jsx
|
||||
import { useState } from "react";
|
||||
function Carte(props) {
|
||||
return (
|
||||
...
|
||||
<button
|
||||
onClick={() => {
|
||||
setLikes(likes + 1); // ❌ "setLikes" et "likes" n'existent PAS dans Carte
|
||||
props.onLike();
|
||||
} // ❌ il manque la ) qui ferme la fonction → erreur de syntaxe
|
||||
>
|
||||
❤️ {likes} // ❌ "likes" non défini
|
||||
</button>
|
||||
```
|
||||
|
||||
Deux problèmes :
|
||||
1. **Erreur de syntaxe** : ta fonction `() => { ... }` n'est jamais refermée par `)`. À elle seule, ça **empêche l'app de s'afficher** (page blanche / erreur de build).
|
||||
2. **`likes` et `setLikes` n'existent pas dans `Carte`** : tu as déplacé les likes dans le **parent** (`App` a bien `useState({...})`), donc `Carte` n'a plus de `useState`. Elle essaie d'utiliser des variables qui ne sont plus là.
|
||||
|
||||
> 🧠 **La cause de fond :** tu as commencé l'approche « **les likes vivent dans le parent** » (bien !) mais tu as gardé des bouts de l'ancienne approche « les likes dans la carte ». Il faut **choisir une seule** approche. On garde celle du parent (c'est celle de la correction précédente).
|
||||
|
||||
---
|
||||
|
||||
## ✅ La règle : `Carte` ne fait qu'**afficher** ce qu'on lui donne
|
||||
|
||||
Dans l'approche « parent », la carte **n'a pas d'état** : elle reçoit `likes` et `onLike` en **props**, c'est tout.
|
||||
|
||||
### `Carte.jsx` corrigé (remplace tout le fichier)
|
||||
```jsx
|
||||
function Carte(props) {
|
||||
return (
|
||||
<div className="carte">
|
||||
<h2>{props.titre}</h2>
|
||||
<p>{props.niveaux}</p>
|
||||
<button onClick={props.onLike}>❤️ {props.likes}</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Carte;
|
||||
```
|
||||
> 🔎 Plus de `import { useState }`, plus de `setLikes`, **un seul** `<h2>` (tu en avais deux : `titre` ET `nom`), et plus de `props.createur` inutile. La carte affiche `props.likes` et, au clic, appelle `props.onLike` — point.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Côté `App.jsx` : finir l'approche « parent »
|
||||
|
||||
Tu as encore l'**ancien** `total` muté qui traîne. On le supprime et on **recalcule** le total.
|
||||
|
||||
```jsx
|
||||
function App() {
|
||||
const [likes, setLikes] = useState({}); // {} au départ (pas {1:3, 4:1})
|
||||
const [recherche, setRecherche] = useState("");
|
||||
// ❌ SUPPRIME : const [total, setTotal] = useState(0);
|
||||
|
||||
// incrémente le like d'UNE trilogie (par son id)
|
||||
function liker(id) {
|
||||
setLikes((prev) => ({ ...prev, [id]: (prev[id] || 0) + 1 }));
|
||||
}
|
||||
|
||||
// le total se RECALCULE à partir des likes
|
||||
const total = Object.values(likes).reduce((s, n) => s + n, 0);
|
||||
|
||||
const filtrees = trilogies.filter((t) =>
|
||||
t.nom.toLowerCase().includes(recherche.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="body">
|
||||
<Header />
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Rechercher une trilogie…"
|
||||
value={recherche}
|
||||
onChange={(e) => setRecherche(e.target.value)}
|
||||
/>
|
||||
<p>👍 Total de likes : {total}</p>
|
||||
{filtrees.length === 0 && <p>Aucune trilogie trouvée</p>}
|
||||
|
||||
{filtrees.map((trilo) => (
|
||||
<Carte
|
||||
key={trilo.id}
|
||||
titre={trilo.nom}
|
||||
niveaux={trilo.niveaux}
|
||||
likes={likes[trilo.id] || 0} // SON nombre de likes
|
||||
onLike={() => liker(trilo.id)} // incrémente SON like
|
||||
/>
|
||||
))}
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Test :** l'app s'affiche de nouveau. Chaque carte montre son ❤️, et le « Total de likes » = la somme, toujours juste.
|
||||
|
||||
> 💡 **À retenir :** dans une appli React, **une donnée vit à UN seul endroit**. Les likes vivent dans `App` ; `Carte` ne fait que les **afficher** (props) et **signaler** un clic (`onLike`). Ne mélange jamais « état dans le parent » et « état dans l'enfant » pour la même donnée — c'est ce qui t'a mis dedans.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Récap
|
||||
|
||||
- [ ] `Carte.jsx` : version « affichage seul » (props `titre`, `niveaux`, `likes`, `onLike`), un seul `<h2>`, plus de `useState`
|
||||
- [ ] `App.jsx` : supprimer l'ancien `total`, ajouter `liker(id)`, recalculer `total` avec `reduce`
|
||||
- [ ] Tester : l'app s'affiche, likes par carte + total correct
|
||||
|
||||
Quand c'est réparé, tu peux passer à **`ENONCE_react_3_services.md`** (la suite). 🚀
|
||||
@@ -0,0 +1,164 @@
|
||||
# 🎮 Énoncé React #3 — formulaire, tri & plusieurs pages
|
||||
|
||||
(À attaquer une fois ton projet réparé via `CORRECTION_carte_likes.md`.)
|
||||
|
||||
Tu sais maintenant : composants, props, état local, **lifting state up**, `useEffect`. On monte d'un cran avec **3 nouveaux gros morceaux**. Comme avant : explications + mini-exemples, à toi d'appliquer sur tes trilogies.
|
||||
|
||||
| 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 une trilogie via un formulaire
|
||||
|
||||
### I.1 — D'abord, rendre la liste **modifiable**
|
||||
Aujourd'hui `trilogies` est un `const` figé. Pour pouvoir **ajouter**, il doit devenir un **état** :
|
||||
```jsx
|
||||
const donneesInitiales = [ /* tes 5 trilogies */ ];
|
||||
|
||||
function App() {
|
||||
const [trilogies, setTrilogies] = useState(donneesInitiales);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### I.2 — Un formulaire contrôlé
|
||||
```jsx
|
||||
const [nom, setNom] = useState("");
|
||||
const [niveaux, setNiveaux] = useState("");
|
||||
|
||||
function ajouter(e) {
|
||||
e.preventDefault(); // empêche le rechargement de page
|
||||
if (nom.trim() === "") return; // on n'ajoute pas du vide
|
||||
const nouvelle = { id: Date.now(), nom, niveaux }; // id unique simple
|
||||
setTrilogies([...trilogies, nouvelle]); // 👈 NOUVEAU tableau = ancien + la nouvelle
|
||||
setNom(""); // on vide les champs
|
||||
setNiveaux("");
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={ajouter}>
|
||||
<input value={nom} onChange={(e) => setNom(e.target.value)} placeholder="Nom de la trilogie" />
|
||||
<input value={niveaux} onChange={(e) => setNiveaux(e.target.value)} placeholder="Niveaux" />
|
||||
<button type="submit">Ajouter</button>
|
||||
</form>
|
||||
);
|
||||
```
|
||||
|
||||
> 🧩 **Le point clé :** on ne « pousse » pas dans le tableau existant (`trilogies.push(...)` ❌). On crée un **nouveau tableau** avec `[...trilogies, nouvelle]` et on le donne à `setTrilogies`. React ne réaffiche que si on lui passe une **nouvelle** valeur. C'est la règle de l'**immutabilité** : on remplace, on ne modifie pas sur place.
|
||||
|
||||
**✅ Test :** remplis le formulaire, clique « Ajouter » → la carte apparaît en bas, les champs se vident, la page ne recharge pas.
|
||||
|
||||
> 🚀 Bonus : un bouton « 🗑️ Supprimer » sur chaque carte → `setTrilogies(trilogies.filter((t) => t.id !== id))` (un nouveau tableau **sans** celle-là).
|
||||
|
||||
---
|
||||
|
||||
## 🔀 Module J — Trier la liste
|
||||
|
||||
Un `<select>` **contrôlé** pour choisir le tri :
|
||||
```jsx
|
||||
const [tri, setTri] = useState("nom");
|
||||
|
||||
const affichees = [...trilogies].sort((a, b) => {
|
||||
if (tri === "nom") return a.nom.localeCompare(b.nom);
|
||||
if (tri === "likes") return (likes[b.id] || 0) - (likes[a.id] || 0);
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<select value={tri} onChange={(e) => setTri(e.target.value)}>
|
||||
<option value="nom">Nom (A→Z)</option>
|
||||
<option value="likes">Plus likées</option>
|
||||
</select>
|
||||
{affichees.map((trilo) => ( /* ... ta Carte ... */ ))}
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
> 🔎 `[...trilogies]` = une **copie** (pour ne pas modifier l'original avec `sort`). Et tu combines avec ton **filtre** de recherche : on filtre **puis** on trie **puis** on `.map()`.
|
||||
|
||||
**✅ Test :** change le tri → l'ordre des cartes change.
|
||||
|
||||
---
|
||||
|
||||
## 🧭 Module K — Plusieurs pages avec React Router
|
||||
|
||||
Jusqu'ici tu as **une seule page**. Pour avoir une page **détail** par trilogie (avec ses niveaux, son nombre de 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="/trilogie/:id" element={<Detail />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
```
|
||||
- `path="/"` → ta page d'accueil (la liste).
|
||||
- `path="/trilogie/:id"` → une page **dynamique** : le `:id` change selon la trilogie.
|
||||
|
||||
### K.4 — Des liens (au lieu de `<a href>`)
|
||||
Dans ta `Carte`, un lien vers le détail :
|
||||
```jsx
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
<Link to={`/trilogie/${props.id}`}>Voir les détails</Link>
|
||||
```
|
||||
> 🔎 `<Link>` navigue **sans recharger** la page (c'est tout l'intérêt d'une « single page app »). `<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 la bonne trilogie par son id, et affiche-la
|
||||
return <h1>Détails de la trilogie {id}</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Test :** clique « Voir les détails » sur une carte → l'URL devient `/trilogie/3` et la page détail s'affiche, **sans rechargement**. Le bouton « précédent » du navigateur te 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 — liste en `useState` + formulaire d'ajout (immutabilité avec `[...]`)
|
||||
- [ ] J — tri via un `<select>` contrôlé
|
||||
- [ ] K — React Router : accueil + page détail (`Link`, `useParams`)
|
||||
|
||||
## 🚀 Encore plus loin
|
||||
- Bouton **supprimer** (`filter`) et **éditer** une trilogie.
|
||||
- Un **custom hook** `useLocalStorage` pour ranger la logique de persistance.
|
||||
- Une vraie **mise en page** CSS de la page détail (image de la trilogie, 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é ? Note ce que tu as tapé + le message exact (console F12). On débloque.
|
||||
Reference in New Issue
Block a user