Recoil: découverte du state management de Facebook

Que vous soyez développeur débutant ou expérimenté en React, vous avez eu besoin à un moment de partager un ou plusieurs états entre différents composants de votre application. Pour répondre à ce besoin vous avez peut-être utilisé la librairie JS la plus populaire, Redux. Inconvénients de cette librairie : son coût de mise en place assez élevé et sa complexité, surtout pour un projet simple.

C’est là qu’entre en jeu Recoil.

Qu'est ce que Recoil ?

C’est une librairie de gestion d’état développée par Facebook et utilisée en interne. Recoil est simple d’utilisation surtout si vous utilisez déjà les hooks. Recoil permet aux composants de souscrire aux states dont ils ont besoin.

Au moment de la rédaction de cet article, Recoil est toujours en phase de développement (version 0.6) sans date de sortie officielle.

Pourquoi l’utiliser ?

Recoil vous servira à partager des données entre vos composants, éviter le props drilling, c’est-à-dire qu’un composant passe une props à un composant enfant sans l’utiliser, et les renders inutiles de composants puisque seuls les composants ayant souscrit au state seront mis à jour.

Il est aussi très facile à mettre en place au sein d’un projet.

Les éléments de base

L’atom

Il représente une partie de l’état de votre application. N’importe quel composant peut y souscrire, à chaque modification de l’atom, un rendu du composant sera effectué.

Le selector

C’est un état dérivé de votre application, concrètement c’est une valeur calculée à partir d’un ou plusieurs atoms, par exemple on peut s’en servir pour filtrer des données.

Les différents hooks

  • useRecoilState : fonctionne comme useState de React, il retourne la valeur de l’atom et un setter pour mettre à jour la valeur de l’atom
  • useRecoilValue : retourne uniquement la valeur de l’atom
  • useSetRecoilState : retourne uniquement la fonction setter de l’atom
  • useResetRecoilState : permet de réinitialiser l’atom à sa valeur par défaut et retourne une fonction pour lancer la réinitialisation

Et sinon, ça s’utilise comment ?

Après la théorie, la pratique !

Pour la suite de cet article vous pouvez télécharger le projet Total Recoil pour tester l’application au fur et à mesure des développements.

Dans cette application, l’utilisateur pourra ajouter des cartes dans son deck puis il pourra les filtrer par couleur ou encore recommencer son deck. La plupart du code est déjà écrit. Il ne vous reste plus qu’à compléter le code en utilisant Recoil.

C’est parti !

Étape 1 : Mise en place de Recoil

npm install recoil

À la racine du projet, on installe la dépendance Recoil.

Modifiez le fichier principal index.tsx pour mettre en place Recoil dans le projet, il suffit d’englober l’application avec la balise RecoilRoot.

import {RecoilRoot} from "recoil";

ReactDOM.render(
    <React.StrictMode>
        <RecoilRoot>
            <App />
        </RecoilRoot>
    </React.StrictMode>,
    document.getElementById('root')
);

Étape 2 : Création de notre deck

Modifiez le fichier CardState/index.ts pour qu’il ressemble à ceci :

import {Card} from "../../models/Card";
import {atom} from "recoil";

export const deckState = atom<Card[]>({
    key: 'deckState',
    default: []
});

Étape 3 : Afficher le deck avec useRecoilState

Une fois l’atom du deck créé, vous pouvez le récupérer dans le composant CardsList pour afficher les cartes. Pour cela, ajoutez les lignes suivantes dans le composant correspondant CardsList.

import {deckState} from "../../state/CardState";
import {useRecoilValue} from "recoil";

ainsi que

const deck = useRecoilValue(deckState);

Le hook useRecoilValue est utilisé ici pour récupérer uniquement la valeur du deck.

À ce moment, vous pouvez lancer l’application pour constater qu’un message s’affiche indiquant que le deck est vide. C’est normal car la valeur par défaut de l’atom deckState est un tableau vide.

Étape 4 : Ajouter une carte au deck avec useSetRecoilState

Pour ajouter une carte au deck, vous utiliserez le hook useSetRecoilState qui va retourner uniquement la méthode permettant de mettre à jour l'atom. Ajoutez les lignes suivantes dans le composant CardForm.

Les imports.

import {useSetRecoilState} from "recoil";
import {deckState} from "../../state/CardState";

Et ce qu’il faut pour ajouter une carte au deck.

const updateDeck = useSetRecoilState(deckState);

const addCard = () => {
    updateDeck(deck => [...deck, card]);
}

Relancez l’application, vous pouvez maintenant ajouter une carte au deck. 🙂

Étape 5 : Filtrer les cartes à afficher avec le selector

Nous allons ajouter la possibilité de filtrer les cartes, par exemple uniquement les cœurs ou uniquement les piques. Pour cela, il faut un atom qui va stocker la valeur du filtre sélectionné et un selector qui sera chargé d’appliquer le filtre sélectionné sur le deck.

Dans le fichier CardState/index.ts, modifiez les imports :

import {Card, CardFilter} from "../../models/Card";
import {atom, selector} from "recoil";


Et ajoutez le code suivant :

export const selectedFilterState = atom<CardFilter>({
    key: 'selectedFilterState',
    default: CardFilter.ALL
});

export const filteredDeckState = selector<Card[]>({
    key: 'filteredDeckState',
    get: opts => {
        const deck = opts.get(deckState);
        const selectedFilter = opts.get(selectedFilterState);

        switch (selectedFilter) {
            case CardFilter.ALL:
                return deck;
            case CardFilter.SPADES_ONLY:
                return deck.filter(card => card.color === 'spades');
            case CardFilter.HEARTS_ONLY:
                return deck.filter(card => card.color === 'hearts');
        }
    }
});

C’est le selector et plus précisément l’attribut get qui va contenir toute  la logique du filtre. On récupère le deck, le filtre sélectionné, puis on applique ce filtre avant de retourner la nouvelle liste filtrée. Cela n’altère en rien le deck initial.

Dans le composant CardFilter, vous pouvez maintenant remplacer le state classique par le state Recoil que vous venez de créer.

import {useRecoilState} from "recoil";
import {selectedFilterState} from "../../state/CardState";

const [selectedFilter, setSelectedFilter] = useRecoilState(selectedFilterState);

Et pour terminer dans le composant CardsList, la source de données du deck n’est plus deckState mais le selector filteredDeckState.

import {filteredDeckState} from "../../state/CardState";

const deck = useRecoilValue(filteredDeckState);

Et voilà le filtre est en place, vous pouvez essayer en relançant l’application.

Étape 6 : Réinitialiser le deck avec useResetRecoilState

Dans le composant CardFilter, modifiez les imports et complétez la méthode resetDeck.

import {useRecoilState, useResetRecoilState} from "recoil";
import {deckState, selectedFilterState} from "../../state/CardState";

const resetDeck = useResetRecoilState(deckState);

Ce hook retourne une méthode qu’il suffit d’appeler telle quelle pour mettre à jour le state avec sa valeur par défaut définie dans l’atom.

Et voilà l’application est entièrement fonctionnelle, vous savez maintenant utiliser les atoms, les selectors et les hooks principaux de Recoil pour partager différents states entre les composants au sein d’une application.

Conclusion

Cette application, aussi simple soit elle, permet de comprendre comment fonctionnent les concepts de base de Recoil, les atoms et les selectors.

Recoil est une librairie de gestion d’états prometteuse, légère et facile à prendre en main. En fonctionnant avec les hooks, elle s’intègre parfaitement dans une application React.