Files
stagiaire/ENONCE_persistance_jeux.md
T

7.5 KiB
Raw Blame History

💾 É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.jsx et Carte.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 localStorage contient déjà des jeux → on les prend. Sinon (première visite) → on va chercher le zelda.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 null et 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>

🧩 filter renvoie 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)),
  );
}

🔎 map parcourt 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 état enEdition qui retient l'id en cours d'édition), et un bouton « Valider ». Plus joli, mais commence par la version prompt.

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 — jeux initialisé depuis localStorage (sentinelle null), 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 useLocalStorage de l'énoncé final fait exactement ce schéma « état + sauvegarde auto ». Une fois la persistance comprise ici, tu pourras remplacer tes blocs useState/useEffect par const [jeux, setJeux] = useLocalStorage("jeux", null).