7.5 KiB
💾 Énoncé — Sauvegarder les JEUX (pas que les likes)
Aujourd'hui, tes likes survivent au refresh (bravo, c'est exactement le bon réflexe localStorage). Mais la liste des jeux, elle, repart du zelda.json à chaque rechargement : tes ajouts disparaissent, et tu ne peux ni supprimer ni modifier durablement.
Objectif : faire vivre la liste jeux comme les likes — initialisée depuis localStorage, sauvegardée à chaque changement — pour pouvoir ajouter, supprimer, modifier et tout retrouver après un F5.
⚠️ On ne touche à rien d'autre que
App.jsx,Accueil.jsxetCarte.jsx. Tu connais déjà toutes les briques : la seule idée neuve est « d'où viennent les jeux la première fois ? ».
🧠 Le seul piège à comprendre d'abord
Pour les likes c'était simple : au tout début ils valent {}.
const [likes] = useState(() => JSON.parse(localStorage.getItem("likes") || "{}"));
Pour les jeux, c'est différent : la première fois, ils ne viennent pas de localStorage (vide) mais du fetch du zelda.json. Donc la logique est :
Si
localStoragecontient déjà des jeux → on les prend. Sinon (première visite) → on va chercher lezelda.json.
Une fois chargés, localStorage devient la source de vérité : c'est lui qu'on relit aux prochains refresh (avec tes ajouts/suppressions/modifs), et le json ne ressert plus.
📝 Étape 1 — Initialiser jeux depuis localStorage (dans App.jsx)
Remplace ton useState([]) par une initialisation paresseuse, avec une sentinelle null qui veut dire « pas encore chargé » :
const [jeux, setJeux] = useState(() => {
const stocke = localStorage.getItem("jeux");
return stocke ? JSON.parse(stocke) : null; // null = jamais chargé
});
🔎 Pourquoi
nullet pas[]? Parce qu'on doit distinguer deux cas : « la liste est vide parce que tu as tout supprimé » ([], à garder !) et « on n'a encore rien chargé » (null, il faut aller chercher le json). Avec[]partout, impossible de faire la différence.
📝 Étape 2 — Aller chercher le json UNE SEULE FOIS si besoin
Ton useEffect de fetch existe déjà. Ajoute juste la condition « seulement si rien n'est encore chargé » :
useEffect(() => {
if (jeux === null) { // rien en localStorage → 1re visite
fetch("/zelda.json")
.then((r) => r.json())
.then(setJeux);
}
}, []); // [] : on ne tente ça qu'au montage
📝 Étape 3 — Sauvegarder à chaque changement
Exactement le même réflexe que pour les likes, mais on ne sauvegarde pas tant que c'est null (sinon on écrirait « null » par-dessus tes données) :
useEffect(() => {
if (jeux !== null) {
localStorage.setItem("jeux", JSON.stringify(jeux));
}
}, [jeux]);
📝 Étape 4 — Gérer l'écran « en cours de chargement »
Tant que jeux vaut null, Accueil ne peut pas faire jeux.length ou jeux.map (ça planterait). Affiche un petit message le temps du fetch. Dans App.jsx, juste avant le return des routes :
if (jeux === null) return <p>Chargement…</p>;
✅ Test de l'étape 1-4 : recharge la page → tout s'affiche normalement. Ouvre les outils dev (F12) → Application → Local Storage → tu dois voir une clé jeux avec tous tes jeux dedans. 🎉
➕ Étape 5 — Ajouter (ça marche déjà… et maintenant ça PERSISTE)
Ta fonction ajouter fait déjà setJeux([...jeux, nouveau]). Tu n'as rien à changer : comme setJeux déclenche le useEffect de l'étape 3, l'ajout est automatiquement sauvegardé.
✅ Test : ajoute un jeu → F5 → il est toujours là.
🛠️ Détail : ton formulaire crée
Type: "3d"en dur. Tu peux ajouter un<select>(2d / 3d) si tu veux choisir, comme pour la console.
🗑️ Étape 6 — Supprimer un jeu
La liste vit dans App, donc la fonction qui supprime doit pouvoir appeler setJeux. Accueil reçoit déjà setJeux en props → on définit supprimer dans Accueil et on la passe à chaque Carte.
Dans Accueil.jsx :
function supprimer(id) {
setJeux(jeux.filter((j) => j.id !== id)); // nouveau tableau SANS ce jeu
}
Puis sur chaque carte, passe la fonction :
<Carte
/* ...les props existantes... */
onDelete={() => supprimer(jeu.id)}
/>
Et dans Carte.jsx, un bouton :
<button onClick={props.onDelete}>🗑️ Supprimer</button>
🧩
filterrenvoie un nouveau tableau contenant tout sauf le jeu visé. On ne modifie jamais l'ancien (immutabilité, comme pour l'ajout). Et grâce à l'étape 3, la suppression est sauvegardée toute seule.
✅ Test : supprime un jeu → F5 → il ne revient pas.
✏️ Étape 7 — Modifier un jeu
Le plus costaud, même principe. On veut remplacer un seul jeu dans la liste en gardant les autres intacts → c'est le boulot de map.
Dans Accueil.jsx :
function modifier(id, nouveauTitre) {
setJeux(
jeux.map((j) => (j.id === id ? { ...j, titre: nouveauTitre } : j)),
);
}
🔎
mapparcourt la liste : pour le jeu visé, il renvoie une copie modifiée ({ ...j, titre: nouveauTitre }) ; pour les autres, il les renvoie tels quels. Résultat : un nouveau tableau où seul un jeu a changé.
Pour l'interface, le plus simple pour commencer : un bouton « ✏️ Renommer » sur la carte qui demande le nouveau titre, par exemple avec prompt (suffisant pour un premier jet) :
<button onClick={() => {
const t = prompt("Nouveau titre ?", props.titre);
if (t) props.onEdit(t);
}}>✏️ Renommer</button>
…avec onEdit={(t) => modifier(jeu.id, t)} passé depuis Accueil.
🚀 Version « propre » (bonus) : un champ
<input>qui apparaît dans la carte quand on clique « modifier » (un étatenEditionqui retient l'id en cours d'édition), et un bouton « Valider ». Plus joli, mais commence par la versionprompt.
✅ Test : renomme un jeu → F5 → le nouveau nom est conservé (et la page Détail l'affiche aussi).
🧯 Le bouton « réinitialiser » (très utile pour debugger)
Quand on bricole localStorage, on aime pouvoir tout remettre à zéro. Un petit bouton :
<button onClick={() => { localStorage.removeItem("jeux"); location.reload(); }}>
♻️ Réinitialiser les jeux
</button>
Il efface la clé jeux → au rechargement, localStorage est vide → on repart du zelda.json (retour à l'étape 2).
✅ Checklist
- 1-4 —
jeuxinitialisé depuislocalStorage(sentinellenull), fetch une seule fois, sauvegarde à chaque changement, écran « Chargement… » - 5 — ajout persistant (rien à changer, ça marche tout seul)
- 6 — suppression (
filter+ bouton sur la carte) - 7 — modification (
map+prompt, puis version<input>en bonus) - bonus — bouton « réinitialiser »
🧠 Concepts
Persistance d'une liste entière dans localStorage · réconcilier fetch (1re fois) vs localStorage (ensuite) avec une sentinelle null · filter (supprimer) et map (modifier) immuables · une fonction d'App passée en props jusqu'à Carte pour agir sur la source de vérité.
🔁 Et si tu veux ranger tout ça proprement : le custom hook
useLocalStoragede l'énoncé final fait exactement ce schéma « état + sauvegarde auto ». Une fois la persistance comprise ici, tu pourras remplacer tes blocsuseState/useEffectparconst [jeux, setJeux] = useLocalStorage("jeux", null).