Dans ce tutoriel je vous propos de découvrir RecoilJS, une librairie qui permet de créer un état partagé entre plusieurs composants.
Avant de se lancer dans la découverte d'une énième librairie il est important de voir les limitations des solutions existantes.
Les contextes
Les contextes sont la première piste à explorer lorsque l'on souhaite partager un état. Ils sont pratiques mais il est nécessaire de créer un nouveau provider pour chaque nouveau contexte. Ce qui peut rapidement devenir complexe.
<UserContextProvider>
<ThemeContextProvider>
<ItemPositionContextProvider>
<App />
</ItemContextProvider>
</ThemeContextProvider>
</UserContextProvider>
De la même manière il n'est pas possible d'ajouter de nouveaux contextes à la volée car on est contraint par la structure de nos éléments.
Redux
Redux convient à la création d'un état centralisé qui pourra ensuite être consommé par des composants enfants. Cependant son architecture peut s'avérer lourde pour des cas simples. et comme pour les contextes il n'est pas possible de créer de nouveaux états à la volée (sans avoir recours à un tableau qui mémorise l'état de chaque composant).
RecoilJS à la rescousse.
L'approche de recoil est bien plus simple et permet de partager des états, nommés atom
, simplement. Comme pour Redux on va entourer notre application avec un composant racine.
import React from 'react'
import { RecoilRoot } from 'recoil'
function App () {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
)
}
Ensuite on va pouvoir créer un atom
, qui sera un bout d'état qui sera extene à la stucture de notre application et qui pourra ensuite être consommé par différents composants.
export const compteurState = atom({
key: 'compteurState', // ID unique ID
default: 0 // Etat initial
})
Cet état pourra ensuite être consommé par n'importe quel composant.
import React from 'react'
import { useRecoilState } from 'recoil'
import { compteurState } from './atoms/compteurState'
function Compteur () {
const [compteur, setCompteur] = useRecoilState(compteurState)
const increment = () => {
setCompteur(n => n + 1)
}
return <button onClick={increment}>Compteur {compteur}</button>
}
Tous les compteurs partageront un même état quelque soit leur position dans la structure de notre application. Vous pouvez aussi simplement récupérer cet état sans forcément le modifier.
function CompteurText () {
const compteur = useRecoilValue(compteurState)
return <p>Compteur {compteur}</p>
}
Il est aussi possible de créer des sélecteurs qui permettent de récupérer des valeurs dérivées depuis un atom.
const charCountState = selector({
key: 'charCountState',
get: ({ get }) => {
return get(textState).length
}
})
Il est ensuite possible de consommer ces sélecteurs comme on le ferait avec un atom.
function CharacterCount () {
const count = useRecoilValue(charCountState)
return <>Taille de la chaine: {count}</>
}
Vous avez aussi la possibilité de créer des atoms à la volée gràce à un paramètre. Par exemple, on souhaite créer plusieurs compteur synchronisés.
const compteurStateFamily = atomFamily({
key: 'compteurFamily',
default: 0
})
Chaque compteur se vera alors attribué un ID et pourra générer à la volée un nouvel atom.
function ElementListItem ({ elementID }) {
const compteur = useRecoilValue(compteurStateFamily(elementID))
return <p>Compteur {compteur}</p>
}
Encore expérimental
Cette approche est encore expérimentale mais une partie de l'API est déjà stabilisée.
La principale problématique de cette approche à l'heure actuelle est l'optimisation de la mémoire (une famille d'atom peut générer un assez grand nombre d'états qui ne seront pas forcément libéré par la suite).