Sauter au contenu

Cours 09 - État global (Zustand)

Développé par Poimandres, Zustand permet de déclarer un état global à l’application (ou plusieurs). Il est ensuite possible de consommer cet état dans n’importe quelle composante client de l’application sans reposer sur le props drilling. React compte aussi sa version native nommée Context. Les deux sont utilisables en parallèle ou en combinaison au besoin.

Contrairement au Context, seules les composantes utilisant la propriété mise à jour effectueront un nouveau rendu. Une modification de n’importe quelle propriété de React Context entraîne un nouveau rendu de toutes les compoosantes qu’il contient.

Fenêtre de terminal
npm install zustand

On crée un store pour regrouper les propriétés et méthodes relative à un service supplémentaire à ajouter à l’application. On pourrait par exemple en créer un pour la gestion du thème affiché dans l’application et pour les fonctionnalités relatives à l’authentification du côté client.

Dans un dossier _stores, on crée donc un fichier par service.

  • Répertoiresrc/
    • Répertoireapp/
      • Répertoire_stores/
        • themeStore.js

En utilisant la fonction create, on crée donc un objet contenant les propriétés et méthodes nécessaires au fonctionnement du service. Le store créé sera utilisé comme un hook React et portera donc le préfixe use:

import { create } from 'zustand';
const useTheme = create((set) => ({
// propriétés
currentTheme: 'dark',
themes: ['dark', 'light', 'contrast'],
currentThemeIndex: 0,
// méthodes à venir
}));

Pour les méthodes devant modifier l’une des propriétés du store (c’est le cas de la plupart des méthodes), on utilise la fonction set récupérée en paramètre. On lui passe un objet ne contenant que les propriétés à modifier et leur nouvelle valeur:

import { create } from 'zustand';
const useTheme = create((set) => ({
// propriétés
currentTheme: 'dark',
themes: ['dark', 'light', 'contrast'],
currentThemeIndex: 0,
// méthodes
setTheme: (selection, index) => set({currentTheme: selection, currentThemeIndex: index})
}));

Si les valeurs actuelles du store sont nécessaires pour obtenir les nouvelles valeurs, on passera plutôt une fonction à set:

import { create } from 'zustand';
const useTheme = create((set) => ({
// propriétés
currentTheme: 'dark',
themes: ['dark', 'light', 'contrast'],
currentThemeIndex: 0,
// méthodes
setTheme: (selection) => set((state) => ({
currentTheme: selection,
currentThemeIndex: state.themes.findIndex((theme) => theme === selection) ?? -1
})),
nextTheme: () => set((state) => {
const index = (state.currentThemeIndex + 1) % state.themes.length;
return {currentTheme: state.themes[index], currentThemeIndex: index};
})
}));

Il reste ensuite à consommer le store en créant un sélecteur pour chaque propriété ou méthode nécessaire à l’utilisation d’une composante (client):

const MaComposante = () => {
// on récupère chaque tranche une à la fois
const currentTheme = useTheme(state => state.currentTheme);
const nextTheme = useTheme(state => state.nextTheme);
return (
<div>
<p>Thème actif: {currentTheme}</p>
<button onClick={nextTheme}>Thème suivant</button>
</div
);
};

Zustand permet aussi de sauvegarder l’état du store dans le localStorage afin d’assurer une persistence des données. Il faut pour cela transformer la création du store en utilisant l’extension persist.

On devra également:

  • donner une nom à la propriété du localStorage sous laquelle on veut sauvegarder l’état
  • sélectionner les propriétés à conserver plutôt que de tout sauvegarder (partialize)
Création du store avec persistence
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
const useTheme = create()(persist((set) => ({
// propriétés
currentTheme: 'dark',
themes: ['dark', 'light', 'contrast'],
currentThemeIndex: 0,
// méthodes
setTheme: (selection) => set((state) => ({
currentTheme: selection,
currentThemeIndex: state.themes.findIndex((theme) => theme === selection) ?? -1
})),
nextTheme: () => set((state) => {
const index = (state.currentThemeIndex + 1) % state.themes.length;
return {currentTheme: state.themes[index], currentThemeIndex: index};
})
})), {
name: 'nom-dans-localstorage',
storage: createJSONStorage(() => sessionStorage),
partialize: (state) => ({currentTheme: state.currentTheme, currentThemeIndex: state.currentT})
});