Aller au contenu
Logo
Image de couverture

React Hooks Personnalisés : useBoolean

Posté le

4 min

Arrivés avec React 16.8, les Hooks permettent de bénéficier d’un état local et d’autres fonctionnalités sans avoir à passer par une classe. Ils nous donnent entre autres la possibilité de se brancher aux cycles de vie de nos composants, comme nous le faisions à l'aide des méthodes componentDidMount ou componentWillUnmount.

Dans cette série d'articles, nous allons découvrir comment implémenter nos propres Hooks personnalisés à partir des Hooks primitifs fournis par React. Cela permettra notamment de réduire la complexité cognitive de nos composants en extrayant de la logique au sein de fonctions dédiées qui pourront être réutilisées à travers le projet. Le code résultant sera plus simple, et nous respecterons le principe de responsabilité unique, plus communément appelé Single Responsability Principle (SRP), qui stipule que chaque classe ou fonction ne doit avoir la responsabilité que d'une seule partie d'une fonctionnalité d'un programme, et doit encapsuler cette partie.

Assez parlé, mettons-nous au travail et implémentions notre premier Hook personnalisé : useBoolean. 😎

Motivation

Tout d'abord, demandons-nous pourquoi nous voudrions implémenter ce Hook. Pour cela, imaginons le composant suivant :

1const Spoil = ({ children }) => {
2  const [spoilVisible, setSpoilVisible] = useState(false);
3
4  return (
5    <div className="spoil">
6      <button onClick={() => setSpoilVisible(visible => !visible)}>
7        {spoilVisible ? "Hide" : "Show"}
8      </button>
9      {spoilVisible && <div className="spoil-content">{children}</div>}
10    </div>
11  );
12};

Ce composant n'affiche ses descendants que lorsque l'utilisateur clique sur le bouton. S'il clique à nouveau sur ce bouton, ils disparaîssent, et ainsi de suite.

Le composant est si simple qu'il n'est pas spécialement difficile à lire, mais nous pourrions améliorer tout de même sa lisibilité en extrayant la fonction d'écoute onClick dans une fonction séparée.

1const Spoil = ({ children }) => {
2  const [showSpoil, setShowSpoil] = useState(false);
3
4  const toggle = () => setShowSpoil((visible) => !visible)
5
6  return (
7    <div className="spoil">
8      <button onClick={toggle}>
9        {showSpoil ? "Hide" : "Show"}
10      </button>
11      {showSpoil && <div className="spoil-content">{children}</div>}
12    </div>
13  );
14};

C'est mieux, mais nous venons d'ajouter une ligne supplémentaire. Dans notre cas, c'est loin d'être un problème, mais si nous travaillons avec des composants plus complexes, cela pourrait très vite aboutir à des déclarations redondantes de fonctions.

Notre composant pourrait être encore plus simplifié si nous avions à notre disposition un Hook useBoolean, qui s'utiliserait de la manière suivante :

1const Spoil = ({ children }) => {
2  const [showSpoil, setShowSpoil] = useBoolean(false);
3
4  return (
5    <div className="spoil">
6      <button onClick={setShowSpoil.toggle}>
7        {showSpoil ? "Hide" : "Show"}
8      </button>
9      {showSpoil && <div className="spoil-content">{children}</div>}
10    </div>
11  );
12};

Vous voyez ? Nous n'avons ajouté aucune fonction supplémentaire et la fonction d'écoute onClick est bien plus facile à lire. C'est là tout l'intérêt d'utiliser des Hooks personnalisés. Nous pouvons maintenant passer à l'implémentation de ce Hook en particulier. 👨🏻‍💻

Implémentation

Commençons par définir le squelette de notre Hook. Pour cela, on utilise useState pour initialiser notre variable et son setter.

1const useBoolean = (initialValue) => {
2	const [value, setValue] = useState(initialValue)
3
4	return [value, setValue]
5}

⚠️ Attention : étant donné que cette fonction (ce Hook) utilise useState, elle ne pourra être utilisée qu'au sein de composants React.

Pour l'instant, nous n'avons fait que définir un alias pour le Hook useState. Pas très intéressant. 😅

C'est à partir de maintenant que les choses deviennent intéressantes. Au lieu de retourner la fonction setValue, nous allons retourner un objet composé des 3 méthodes suivantes :

  • toggle() pour inverser la valeur
  • on() pour définir la valeur à true
  • off() pour définir la valeur à false

Voici à quoi ressemble désormais notre Hook :

1const useBoolean = (initialValue) => {
2	const [value, setValue] = useState(initialValue)
3
4	const updateValue = useRef({
5		toggle: () => setValue(oldValue => !oldValue),
6		on: () => setValue(true),
7		off: () => setValue(false)
8	})
9
10	return [value, updateValue.current]
11}

Nous avons encapsulé l'objet dans le Hook useRef pour empêcher React de le récréer à chaque nouveau rendu du composant appelant. Pour plus d'informations, n'hésitez-pas à consulter la documentation officielle.

Et voilà, vous venez de créer votre tout premier Hook personnalisé, félicitations ! 🥳

Utilisation

Il ne nous reste plus qu'à utiliser notre Hook nouvellement créé. Pour cela, on l'appelle de la même manière que l'on appellerait useState, et on utilise les méthodes qui nous sont renvoyées dans l'objet de retour.

1const Articles = () => {
2  const [articles, setArticles] = useState([])
3  const [isLoading, setIsLoading] = useBoolean(false)
4  const [isError, setIsError] = useBoolean(false)
5
6  useEffect(() => {
7    setIsLoading.on()
8    fetch(someApiEndpoint)
9      .then(res => res.json())
10      .then(setArticles)
11      .catch(setIsError.on)
12      .finally(setIsLoading.off)
13  }, [])
14
15  return // ...
16}

⚠️ Attention : on ne peut plus utiliser setIsLoading(true) car nous n'exportons plus une fonction, mais bien un objet.

Constatez par vous-même que le code ci-dessus est incroyablement facile à lire. 😎

Conclusion

Nous avons découvert comment abstraire de la logique de nos composants afin de les rendre plus simples et plus faciles à lire. C'est la force principale des Hooks personnalisés. Dans le prochain article, nous implémenterons un autre Hook qui aura également son utilité : useCounter.


Références

Cet article t'a plu ?

N'hésite-pas à le partager sur les réseaux ! 😊

Copyright © 2022 Ludovic CHOMBEAU