Aller au contenu

b

Hooks personnalisés

Hooks

React offre 15 hooks intégrés différents, parmi lesquels les plus populaires sont les hooks useState et useEffect que nous avons déjà beaucoup utilisés.

Dans la partie 5, nous avons utilisé le hook useImperativeHandle qui permet aux composants de fournir leurs fonctions à d'autres composants. Dans la partie 6, nous avons utilisé useReducer et useContext pour implémenter une gestion d'état à la manière de Redux.

Au cours des dernières années, de nombreuses bibliothèques React ont commencé à offrir des API basées sur les hooks. Dans la partie 6, nous avons utilisé les hooks useSelector et useDispatch de la bibliothèque react-redux pour partager notre redux-store et la fonction dispatch avec nos composants.

L'API de React Router que nous avons introduite dans la partie précédente est également partiellement basée sur les hooks. Ses hooks peuvent être utilisés pour accéder aux paramètres de l'URL et à l'objet de navigation, qui permet de manipuler l'URL du navigateur de manière programmatique.

Comme mentionné dans la partie 1, les hooks ne sont pas des fonctions normales, et lorsque nous les utilisons, nous devons adhérer à certaines règles ou limitations. Récapitulons les règles d'utilisation des hooks, copiées mot pour mot de la documentation officielle de React:

Ne pas appeler les Hooks dans des boucles, conditions, ou fonctions imbriquées. Au lieu de cela, utilisez toujours les Hooks au niveau supérieur de votre fonction React.

Ne pas appeler les Hooks depuis des fonctions JavaScript classiques. Au lieu de cela, vous pouvez:

  • Appeler les Hooks depuis les composants de fonction React.
  • Appeler les Hooks depuis des hooks personnalisés

Il existe un plugin ESLint qui peut être utilisé pour vérifier que l'application utilise correctement les hooks:

erreur vscode useState appelé conditionnellement

Hooks personnalisés

React offre la possibilité de créer des hooks personnalisés. Selon React, l'objectif principal des hooks personnalisés est de faciliter la réutilisation de la logique utilisée dans les composants.

Construire vos propres Hooks vous permet d'extraire la logique des composants en fonctions réutilisables.

Les hooks personnalisés sont des fonctions JavaScript régulières qui peuvent utiliser n'importe quel autre hook, tant qu'ils adhèrent aux règles des hooks. De plus, le nom des hooks personnalisés doit commencer par le mot use.

Nous avons implémenté une application de compteur dans la partie 1 qui peut voir sa valeur incrémentée, décrémentée ou réinitialisée. Le code de l'application est le suivant:

import { useState } from 'react'
const App = () => {
  const [counter, setCounter] = useState(0)

  return (
    <div>
      <div>{counter}</div>
      <button onClick={() => setCounter(counter + 1)}>
        plus
      </button>
      <button onClick={() => setCounter(counter - 1)}>
        minus
      </button>      
      <button onClick={() => setCounter(0)}>
        zero
      </button>
    </div>
  )
}

Extrayons la logique du compteur dans un hook personnalisé. Le code du hook est le suivant:

const useCounter = () => {
  const [value, setValue] = useState(0)

  const increase = () => {
    setValue(value + 1)
  }

  const decrease = () => {
    setValue(value - 1)
  }

  const zero = () => {
    setValue(0)
  }

  return {
    value, 
    increase,
    decrease,
    zero
  }
}

Notre hook personnalisé utilise en interne le hook useState_ pour créer son état. Le hook retourne un objet, dont les propriétés incluent la valeur du compteur ainsi que des fonctions pour manipuler la valeur.

Les composants React peuvent utiliser le hook comme montré ci-dessous:

const App = () => {
  const counter = useCounter()

  return (
    <div>
      <div>{counter.value}</div>
      <button onClick={counter.increase}>
        plus
      </button>
      <button onClick={counter.decrease}>
        minus
      </button>      
      <button onClick={counter.zero}>
        zero
      </button>
    </div>
  )
}

En faisant cela, nous pouvons extraire entièrement l'état du composant App et sa manipulation dans le hook useCounter. La gestion de l'état et de la logique du compteur est maintenant la responsabilité du hook personnalisé.

Le même hook pourrait être réutilisé dans l'application qui gardait la trace du nombre de clics effectués sur les boutons gauche et droit:

const App = () => {
  const left = useCounter()
  const right = useCounter()

  return (
    <div>
      {left.value}
      <button onClick={left.increase}>
        left
      </button>
      <button onClick={right.increase}>
        right
      </button>
      {right.value}
    </div>
  )
}

L'application crée deux compteurs complètement séparés. Le premier est affecté à la variable left et l'autre à la variable right.

Gérer les formulaires dans React est quelque peu délicat. L'application suivante présente à l'utilisateur un formulaire lui demandant de saisir son nom, sa date de naissance et sa taille:

const App = () => {
  const [name, setName] = useState('')
  const [born, setBorn] = useState('')
  const [height, setHeight] = useState('')

  return (
    <div>
      <form>
        name: 
        <input
          type='text'
          value={name}
          onChange={(event) => setName(event.target.value)} 
        /> 
        <br/> 
        birthdate:
        <input
          type='date'
          value={born}
          onChange={(event) => setBorn(event.target.value)}
        />
        <br /> 
        height:
        <input
          type='number'
          value={height}
          onChange={(event) => setHeight(event.target.value)}
        />
      </form>
      <div>
        {name} {born} {height} 
      </div>
    </div>
  )
}

Chaque champ du formulaire a son propre état. Pour maintenir l'état du formulaire synchronisé avec les données fournies par l'utilisateur, nous devons enregistrer un gestionnaire onChange approprié pour chacun des éléments input.

Définissons notre propre hook personnalisé useField qui simplifie la gestion de l'état du formulaire:

const useField = (type) => {
  const [value, setValue] = useState('')

  const onChange = (event) => {
    setValue(event.target.value)
  }

  return {
    type,
    value,
    onChange
  }
}

La fonction du hook reçoit le type du champ de saisie en paramètre. La fonction retourne tous les attributs requis par l'élément input: son type, sa valeur et le gestionnaire onChange.

Le hook peut être utilisé de la manière suivante:

const App = () => {
  const name = useField('text')
  // ...

  return (
    <div>
      <form>
        <input
          type={name.type}
          value={name.value}
          onChange={name.onChange} 
        /> 
        // ...
      </form>
    </div>
  )
}

Propagation d'attributs

Nous pourrions simplifier les choses encore un peu plus. Puisque l'objet name possède exactement tous les attributs que l'élément input s'attend à recevoir comme props, nous pouvons passer les props à l'élément en utilisant la syntaxe de propagation de la manière suivante:

<input {...name} /> 

Comme l'exemple dans la documentation React l'indique, les deux manières suivantes de passer des props à un composant aboutissent exactement au même résultat:

<Greeting firstName='Arto' lastName='Hellas' />

const person = {
  firstName: 'Arto',
  lastName: 'Hellas'
}

<Greeting {...person} />

L'application est simplifiée dans le format suivant:

const App = () => {
  const name = useField('text')
  const born = useField('date')
  const height = useField('number')

  return (
    <div>
      <form>
        name: 
        <input  {...name} /> 
        <br/> 
        birthdate:
        <input {...born} />
        <br /> 
        height:
        <input {...height} />
      </form>
      <div>
        {name.value} {born.value} {height.value}
      </div>
    </div>
  )
}

Gérer les formulaires est grandement simplifié lorsque les détails désagréables liés à la synchronisation de l'état du formulaire sont encapsulés à l'intérieur de notre hook personnalisé.

Les hooks personnalisés ne sont pas seulement un outil de réutilisation ; ils fournissent également une meilleure manière de diviser notre code en parties modulaires plus petites.

Plus sur les hooks

Internet commence à se remplir de plus en plus de matériel utile lié aux hooks. Les sources suivantes valent la peine d'être consultées: