Skip to content

Observateurs

Dans le chapitre précédent, nous avons vu qu'il était possible d'observer les changements d'état de certaines données afin de recalculer dynamiquement une valeur qui en dépend. C'est le concept de donnée calculée.

Toutefois, il arrive parfois que l'on souhaite observer un changement d'état pour d'autres raisons que le simple calcul d'une valeur. Par exemple, on peut vouloir envoyer une requête à une API ou stocker en cache, dans le navigateur, une valeur à la suite de son changement d'état.

Dans ce chapitre, nous allons découvrir comment observer le changement d'état d'une donnée réactive grâce aux observateurs (watchers).

Définition

Local Storage

Admettons que nous souhaitions stocker la valeur d'une variable en cache, sur le navigateur, afin de la récupérer lors de la prochaine visite de l'utilisateur sur l'application. Pour cela, nous pouvons utiliser l'objet localStorage de JavaScript :

js
const myVar = Number(localStorage.getItem("myVar") || 0)
...
// if myVar change, we edit the cache
localeStorage.setItem("myVar", myVar)

myVar est instancié en tentant de récupérer une valeur stockée en cache associé à la clé "myVar". Si aucune valeur n'est trouvée dans le cache, la variable vaudra par défaut 0.

INFO

Le local storage ne stocke que des valeurs sous forme de chaînes de caractères. C'est pourquoi nous utilisons Number pour convertir la valeur récupérée en cache en un nombre.

Si la valeur de notre variable change, nous souhaitons actualiser la valeur en cache à l'aide de la méthode setItem.

Déclarer un observateur

Si notre variable devient une donnée réactive ou une props, nous pouvons utiliser un observateur (watcher) pour détecter facilement son changement d'état :

jsx
import { useState, useEffect } from "react";

export default function App() {
  const [count, setCount] = useState(
    Number(localStorage.getItem("count") || 0)
  );

  const increment = () => setCount((oldCount) => oldCount + 1);

  useEffect(() => {
    console.log("Count value changed :", count);
    localStorage.setItem("count", count);
  }, [count]);

  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>Add</button>
    </div>
  );
}

Dans cette implémentation d'un compteur, la donnée count est instanciée en récupérant une valeur du local storage. Si le cache est vide, count vaut 0.

La fonction useEffect de React.js est ensuite utilisée pour observer les changements d'état de count. useEffect prend en premier argument une fonction à exécuter lorsqu'un changement intervient. Comme pour useMemo, la fonction prend en second argument la liste des données réactives à observer, ici uniquement count. Si la donnée count change de valeur, la fonction fournie à useEffect sera automatiquement relancée.

WARNING

⚠️ Attention, la fonction passée en argument de useEffect est toujours exécutée une première fois lors de l'initialisation du composant. (voir le chapitre Cycle de vie)

Nous indiquons ici qu'à chaque fois que la donnée count est modifiée, nous souhaitons mettre à jour le local storage afin qu'il stocke la nouvelle valeur de la donnée.

🐶 Dog Center

Complétons l'implémentation des champs de recherche dans notre galerie, afin que la dernière recherche et l'ordre dans lequel les chiens sont affichés soient conservés si l'utilisateur revient plus tard sur le site. Il pourra ainsi retrouver la galerie dans le même état que lors de sa dernière visite.

jsx
import "./App.css";
import { useState, useMemo, useEffect } from "react";
import dogsData from "./dogsData";
import NewsSlider from "./components/NewsSlider";
import DogCard from "./components/DogCard";


export default function App() {
  const [search, setSearch] = useState(
    localStorage.getItem("search") || ""
  );
  const [dogsSortBy, setDogsSortBy] = useState(
    localStorage.getItem("dogsSortBy") || "age"
  );

  useEffect(() => {
    localStorage.setItem("search", search)
  }, [search])

  useEffect(() => {
    localStorage.setItem("dogsSortBy", dogsSortBy)
  }, [dogsSortBy])

  const filteredDogsData = useMemo(...);

  return (
    <div>
        ...
    </div>
  );
};

Nous ne modifions ici que la partie JavaScript du composant. Les données search et dogsSortBy sont désormais instanciées avec une valeur en cache ou une valeur par défaut.

Des observateurs sont ensuite déclarés pour actualiser le cache à chaque fois qu'une des données change de valeur.

Si nous quittons l'application puis revenons sur le site, l'ordre et la recherche affectant la galerie sont bien conservés.

Conclusion

L'usage des observateurs vient compléter les possibilités offertes par les données réactives. Il nous est maintenant possible d'observer l'état d'une donnée afin d'y réagir, autrement que par le recalcul d'une autre donnée, comme avec les données calculées.

Notez bien que les observateurs sont dédiés à la gestion d'effets souvent plus en "périphérie" (side-effects) de l'application, comme la gestion de cache, ou l'envoi d'informations de suivi à un serveur.

Pour exprimer les dépendances entre différents états dans une application, on privilégiera toujours l'usage des données calculées.

Dans le prochain chapitre, nous développerons plus en détail la façon dont le framework gère la réactivité au niveau des composants, à travers le concept de cycle de vie.