Aller au contenu principal

Module 8 – Gestion d’état (React & Vue)

Niveau 5.2 – Laravel Inertia (React & Vue)


Objectif

À la fin de ce module vous saurez :

  • Distinguer état venant de Laravel (props de page, shared data) et état local (UI : modale ouverte, onglet actif, valeur d’un champ avant soumission).
  • Gérer l’état local en React (useState, useReducer, Context) et en Vue (ref, reactive, Pinia).
  • Décider quand garder l’état côté client et quand repasser par Laravel (navigation, rechargement de props).

Nous donnons des exemples : filtre local avant « Appliquer », modale de confirmation, onglets dans une page, et bonnes pratiques pour éviter la duplication de logique.


Deux types d’état dans une app Inertia

1. État « page » (données serveur)

  • Source : Laravel, via Inertia::render() (props de la page) ou share() (auth, flash).
  • Vie : tant que vous restez sur la même « visite » de page ; à la navigation suivante, les props peuvent changer (nouvelle page = nouvelles props).
  • Exemples : liste d’utilisateurs, détail d’un article, utilisateur connecté.
  • Modification : en faisant une nouvelle requête (lien, formulaire, router.get avec paramètres). Laravel renvoie de nouvelles props.

2. État « local » (UI, brouillon)

  • Source : uniquement le JavaScript (React ou Vue) dans le navigateur.
  • Vie : limitée au composant (ou à l’arbre de composants qui partagent un Context / store). Rechargement de la page = perte de cet état (sauf si vous le persistez en localStorage/sessionStorage).
  • Exemples : une modale est ouverte ou fermée, l’onglet actif (« Infos » / « Commentaires »), la valeur d’un champ de recherche avant de cliquer sur « Rechercher » (tant qu’on n’a pas envoyé la requête au serveur).
  • Modification : setState (React) ou ref.value = ... (Vue), sans appel au serveur.

Inertia ne remplace pas la gestion d’état locale : vous utilisez les outils habituels de React ou Vue pour tout ce qui est purement interface (ouvert/fermé, sélection, brouillon).


React – useState pour l’état local

Exemple : modale de confirmation avant suppression

import React, { useState } from 'react';
import { Link, useForm } from '@inertiajs/react';

export default function UsersIndex({ users }) {
const [modalOpen, setModalOpen] = useState(false);
const [userToDelete, setUserToDelete] = useState(null);
const { delete: destroy } = useForm();

const openModal = (user) => {
setUserToDelete(user);
setModalOpen(true);
};

const closeModal = () => {
setModalOpen(false);
setUserToDelete(null);
};

const confirmDelete = () => {
if (userToDelete) {
destroy(route('users.destroy', userToDelete.id));
closeModal();
}
};

return (
<div>
<ul>
{users.data.map((user) => (
<li key={user.id}>
{user.name}
<button type="button" onClick={() => openModal(user)}>
Supprimer
</button>
</li>
))}
</ul>

{modalOpen && (
<div className="modal-overlay">
<div className="modal">
<p>Supprimer {userToDelete?.name} ?</p>
<button onClick={confirmDelete}>Oui</button>
<button onClick={closeModal}>Non</button>
</div>
</div>
)}
</div>
);
}
  • modalOpen et userToDelete sont de l’état local : ils ne viennent pas de Laravel et ne sont pas renvoyés au serveur tant qu’on ne soumet pas la suppression.
  • confirmDelete déclenche une requête Inertia (suppression) puis ferme la modale.

Exemple : onglets (Infos / Commentaires)

const [activeTab, setActiveTab] = useState('infos');

return (
<div>
<button onClick={() => setActiveTab('infos')}>Infos</button>
<button onClick={() => setActiveTab('comments')}>Commentaires</button>
{activeTab === 'infos' && <div>...</div>}
{activeTab === 'comments' && <div>...</div>}
</div>
);

Aucune requête serveur : simple affichage conditionnel.


Vue – ref et reactive pour l’état local

Exemple : modale de confirmation (Vue)

<template>
<div>
<ul>
<li v-for="user in users.data" :key="user.id">
{{ user.name }}
<button type="button" @click="openModal(user)">Supprimer</button>
</li>
</ul>

<div v-if="modalOpen" class="modal-overlay">
<div class="modal">
<p>Supprimer {{ userToDelete?.name }} ?</p>
<button @click="confirmDelete">Oui</button>
<button @click="closeModal">Non</button>
</div>
</div>
</div>
</template>

<script setup>
import { ref } from 'vue';
import { useForm, router } from '@inertiajs/vue3';

const props = defineProps({ users: Object });

const modalOpen = ref(false);
const userToDelete = ref(null);

function openModal(user) {
userToDelete.value = user;
modalOpen.value = true;
}

function closeModal() {
modalOpen.value = false;
userToDelete.value = null;
}

function confirmDelete() {
if (userToDelete.value) {
router.delete(route('users.destroy', userToDelete.value.id));
closeModal();
}
}
</script>
  • ref(false) et ref(null) : état local réactif. En script on utilise .value pour lire/écrire ; dans le template, Vue déréférence automatiquement.
  • Même logique qu’en React : la modale et l’utilisateur sélectionné sont purement côté client jusqu’à l’appel router.delete.

État partagé entre plusieurs composants (React Context, Pinia)

Quand plusieurs composants (plusieurs pages ou plusieurs branches du layout) ont besoin du même état local (ex. panier, thème clair/sombre), vous pouvez :

  • React : Context + useState (ou useReducer) dans un provider, ou une librairie type Zustand, Jotai.
  • Vue : provide/inject ou un store Pinia (recommandé en Vue 3).

Ces états restent côté client : Inertia ne les envoie pas au serveur. Si vous devez persister le panier ou les préférences, il faudra un jour les envoyer à Laravel (formulaire, API dédiée, ou stockage en BDD après connexion). Inertia ne gère que le flux « page → props » ; le reste est du React/Vue classique.


Filtres : état local vs requête serveur

Pour un champ de recherche ou des filtres (catégorie, date) :

  • Option A : champ non contrôlé par une requête immédiate. L’utilisateur saisit, puis clique sur « Appliquer » ou « Rechercher ». Jusqu’au clic, la valeur est en état local ; au clic, vous faites router.get(url, { search: value }) et Laravel renvoie une nouvelle page avec les résultats. Les props de la page (liste filtrée) viennent alors du serveur.
  • Option B : debounce : à chaque changement du champ (avec un délai), vous appelez router.get avec les paramètres. L’état « valeur du champ » peut être local (pour l’input) ; les résultats viennent des props après chaque requête.

Dans les deux cas, ne dupliquez pas la logique de filtrage côté client : le serveur reste la source de vérité pour les données (liste filtrée, paginée). L’état local ne sert qu’à la saisie et au déclenchement de la requête.


À retenir

  • Props de page et shared data = état serveur (Laravel). useState / ref = état local (UI, brouillon).
  • Modales, onglets, sélection temporaire : état local en useState (React) ou ref (Vue).
  • État partagé entre composants : Context ou store (React), Pinia ou provide/inject (Vue).
  • Filtres / recherche : garder la valeur du champ en local ; déclencher router.get pour mettre à jour les données (props) venant de Laravel. Ne pas refaire la logique de filtrage côté client.

Dans le prochain module, nous verrons l’authentification (login, register, routes protégées) avec Inertia et Laravel.