# React Hooks Personnalisés : useBoolean

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 :

```jsx
const Spoil = ({ children }) => {
  const [spoilVisible, setSpoilVisible] = useState(false);

  return (
    <div className="spoil">
      <button onClick={() => setSpoilVisible(visible => !visible)}>
        {spoilVisible ? "Hide" : "Show"}
      </button>
      {spoilVisible && <div className="spoil-content">{children}</div>}
    </div>
  );
};
```

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.

```jsx
const Spoil = ({ children }) => {
  const [showSpoil, setShowSpoil] = useState(false);

  const toggle = () => setShowSpoil((visible) => !visible)

  return (
    <div className="spoil">
      <button onClick={toggle}>
        {showSpoil ? "Hide" : "Show"}
      </button>
      {showSpoil && <div className="spoil-content">{children}</div>}
    </div>
  );
};
```

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 :

```jsx
const Spoil = ({ children }) => {
  const [showSpoil, setShowSpoil] = useBoolean(false);

  return (
    <div className="spoil">
      <button onClick={setShowSpoil.toggle}>
        {showSpoil ? "Hide" : "Show"}
      </button>
      {showSpoil && <div className="spoil-content">{children}</div>}
    </div>
  );
};
```

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.

```jsx
const useBoolean = (initialValue) => {
	const [value, setValue] = useState(initialValue)

	return [value, setValue]
}
```

> **⚠️ 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 :

```jsx
const useBoolean = (initialValue) => {
	const [value, setValue] = useState(initialValue)

	const updateValue = useRef({
		toggle: () => setValue(oldValue => !oldValue),
		on: () => setValue(true),
		off: () => setValue(false)
	})

	return [value, updateValue.current]
}
```

>  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](https://fr.reactjs.org/docs/hooks-reference.html#useref).

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.

```jsx
const Articles = () => {
  const [articles, setArticles] = useState([])
  const [isLoading, setIsLoading] = useBoolean(false)
  const [isError, setIsError] = useBoolean(false)

  useEffect(() => {
    setIsLoading.on()
    fetch(someApiEndpoint)
      .then(res => res.json())
      .then(setArticles)
      .catch(setIsError.on)
      .finally(setIsLoading.off)
  }, [])

  return // ...
}
```

> **⚠️ 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

- https://fr.reactjs.org/docs/hooks-intro.html
- https://en.wikipedia.org/wiki/Single-responsibility_principle

