Étape 7 — Connecter les pages au store
Objectifs
- Appeler
pokemonStore.init()dansApp.vuepour charger les données une seule fois - Remplacer les
fetch()locaux par le store dansindex.vueet[id].vue - Comprendre
storeToRefs()et pourquoi c'est nécessaire - Utiliser un getter avec paramètre (
getPokemonById)
Contexte
Le store est créé, mais aucune page ne l'utilise encore. Actuellement :
index.vuefait son proprefetch()dansonMounted[id].vuefait aussi son proprefetch()dansonMounted
On va remplacer tout ça par le store. Le chargement se fera une seule fois dans App.vue, et les pages liront simplement les données du store.
Tâches
1. Initialiser le store dans App.vue
App.vue est le composant racine — il est monté une seule fois au démarrage. C'est l'endroit idéal pour initialiser le store.
Modifiez App.vue pour :
- Importer
usePokemonStoredepuis@/stores/pokemonStore - Dans
onMounted, appelerpokemonStore.init()
import { usePokemonStore } from '@/stores/pokemonStore'
onMounted(async () => {
const pokemonStore = usePokemonStore()
await pokemonStore.init()
})Pourquoi dans App.vue et pas dans main.js ?
On pourrait techniquement appeler init() dans main.js, mais onMounted dans App.vue est plus explicite : on voit clairement quand les données sont chargées dans le cycle de vie de l'application.
2. Modifier index.vue — Utiliser le store
Remplacez tout le code de chargement (ref, onMounted, fetch) par le store :
Avant (séquence 1) :
const pokemons = ref([])
onMounted(async () => {
const response = await fetch('http://localhost:3535/pokemons')
pokemons.value = await response.json()
})Après (séquence 2) :
import { usePokemonStore } from '@/stores/pokemonStore'
import { storeToRefs } from 'pinia'
const pokemonStore = usePokemonStore()
const { pokemons } = storeToRefs(pokemonStore)Le onMounted et le ref disparaissent de index.vue. Plus besoin : c'est App.vue qui charge les données.
Encart oral — storeToRefs : pourquoi c'est nécessaire
Quand on destructure un store Pinia, les propriétés perdent leur réactivité :
// ❌ MAUVAIS — pokemons n'est plus réactif
const { pokemons } = pokemonStorestoreToRefs() convertit chaque propriété du state en ref, ce qui préserve la réactivité :
// ✅ BON — pokemons reste réactif
const { pokemons } = storeToRefs(pokemonStore)Règle simple : utiliser storeToRefs() pour le state et les getters. Les actions se destructurent directement car ce sont des fonctions (pas des données réactives).
// State et getters → storeToRefs
const { pokemons, types, isLoading } = storeToRefs(pokemonStore)
// Actions → destructuration directe
const { init, fetchPokemons } = pokemonStoreDans le template, remplacez pokemons par la variable destructurée :
<v-col
v-for="pokemon in pokemons"
:key="pokemon.id"
cols="12" sm="6" md="4" lg="3"
>
<pokemon-card :pokemon="pokemon" />
</v-col>Alternative sans storeToRefs
Vous pouvez aussi accéder aux données directement via le store sans destructurer :
<v-col v-for="pokemon in pokemonStore.pokemons" ...>Dans ce cas, pas besoin de storeToRefs. C'est plus simple mais plus verbeux dans le template. Les deux approches sont valides.
3. Modifier [id].vue — Utiliser le getter
Remplacez le fetch() local par le getter getPokemonById du store :
Avant (séquence 1) :
const route = useRoute()
const pokemons = ref([])
const pokemon = computed(() => {
return pokemons.value.find(p => p.id === route.params.id)
})
onMounted(async () => {
const response = await fetch('http://localhost:3535/pokemons')
pokemons.value = await response.json()
})Après (séquence 2) :
import { usePokemonStore } from '@/stores/pokemonStore'
const route = useRoute()
const pokemonStore = usePokemonStore()
const pokemon = computed(() => {
return pokemonStore.getPokemonById(route.params.id)
})Le ref, le onMounted et le fetch disparaissent. Le computed utilise le getter du store qui cherche dans les données déjà chargées.
Le double fetch() est résolu !
Rappelez-vous le warning de l'étape 4 : on appelait fetch() deux fois (une dans index.vue, une dans [id].vue). Maintenant, App.vue appelle init() une seule fois et les deux pages lisent le même store. Problème résolu.
Projet perso — À faire en parallèle
Dans votre projet individuel :
- Initialisez votre store dans
App.vueaveconMounted - Remplacez les
fetch()locaux par le store dans votre page liste - Utilisez un getter pour la page de détail
- Testez que
storeToRefs()fonctionne (ou accédez directement via le store)
Tests
App.vueappellepokemonStore.init()dansonMountedindex.vuen'a plus defetch()ni deonMountedindex.vueutilisestoreToRefs()(oupokemonStore.pokemonsdirectement)- La grille de Pokémon s'affiche comme avant
[id].vuen'a plus defetch()ni deonMounted[id].vueutilisepokemonStore.getPokemonById()- La page de détail affiche le bon Pokémon
- Navigation accueil → détail → retour fonctionne
- 0 erreurs dans la console
- L'onglet Pinia dans Vue DevTools montre les données du store
Solutions
src/App.vue
<template>
<v-app>
<app-header />
<v-main>
<router-view />
</v-main>
<app-footer />
</v-app>
</template>
<script setup>
// Imports des composants de layout
import AppHeader from '@/components/AppHeader.vue'
import AppFooter from '@/components/AppFooter.vue'
// Import du store Pokémon
import { usePokemonStore } from '@/stores/pokemonStore'
// Initialisation du store au montage de l'application
// onMounted est auto-importé grâce à unplugin-auto-import
onMounted(async () => {
const pokemonStore = usePokemonStore()
// init() charge les Pokémon et les types en parallèle
await pokemonStore.init()
})
</script>src/pages/index.vue
<template>
<v-container>
<h1 class="text-h3 text-center my-6">Pokédex</h1>
<v-row>
<v-col
v-for="pokemon in pokemons"
:key="pokemon.id"
cols="12"
sm="6"
md="4"
lg="3"
>
<pokemon-card :pokemon="pokemon" />
</v-col>
</v-row>
</v-container>
</template>
<script setup>
// Import du store et de storeToRefs
import { usePokemonStore } from '@/stores/pokemonStore'
import { storeToRefs } from 'pinia'
import PokemonCard from '@/components/PokemonCard.vue'
// Instancier le store
const pokemonStore = usePokemonStore()
// Destructurer le state en gardant la réactivité
// storeToRefs convertit chaque propriété du state en ref
const { pokemons } = storeToRefs(pokemonStore)
</script>src/pages/pokemon/[id].vue
<template>
<v-container>
<v-btn
variant="text"
prepend-icon="mdi-arrow-left"
class="mb-4"
@click="$router.back()"
>
Retour
</v-btn>
<v-alert
v-if="!pokemon"
type="error"
variant="tonal"
>
Pokémon non trouvé.
</v-alert>
<v-card
v-else
max-width="800"
class="mx-auto"
>
<v-img
:src="getImageUrl(pokemon.img)"
:alt="pokemon.name"
height="300"
cover
/>
<v-card-title class="text-h4">
{{ pokemon.name }}
</v-card-title>
<v-card-subtitle>
Niveau {{ pokemon.level }}
</v-card-subtitle>
<v-card-text>
<p v-if="pokemon.description" class="text-body-1 mb-4">
{{ pokemon.description }}
</p>
</v-card-text>
</v-card>
</v-container>
</template>
<script setup>
import { getImageUrl } from '@/utils/imageUrl'
// Import du store Pokémon
import { usePokemonStore } from '@/stores/pokemonStore'
// Récupérer l'ID depuis les paramètres de la route
const route = useRoute()
// Instancier le store
const pokemonStore = usePokemonStore()
// Utiliser le getter du store pour trouver le Pokémon
// computed se met à jour automatiquement si l'ID change
const pokemon = computed(() => {
return pokemonStore.getPokemonById(route.params.id)
})
</script>Commit
git add -A
git commit -m "feat: connexion des pages au store Pinia"