Maîtrisez React

Découvrez React et son écosystème, quel que soit votre niveau.

Premiers pas avec les hooks de React

February 13, 2019

Les hooks sont probablement la nouveauté récente de React qui va le plus révolutionner les pratiques dans l’écriture de composants. Petit tour de quelques fonctionnalités qu’ils permettent.

Cet article est adapté d’un extrait du livre Des applications modernes avec React, tiré du chapitre Écrire des composants réutilisables.

Fin 2018 à l’occasion de la React Conf, Facebook a annoncé l’ajout d’une fonction à React qui a séduit la plupart des développeurs : les hooks. La philosophie est la suivante : tout composant devrait pouvoir s’écrire sous une forme fonctionnelle pure, c’est-à-dire typiquement :

  • qu’il ne possède pas d’état interne ;
  • qu’il n’a pas d’effet de bord (notamment il n’a donc pas besoin d’accéder aux méthodes de cycle de vie componentDidMount, etc.) ;
  • qu’il est déclaré sous forme de fonction et non de classe.

Les hooks permettent de faire en sorte que tout « ajout » à de tels composants (état, cycle de vie) passe non pas par le fait de réarchitecturer le composant (sous forme d’une classe), mais simplement d’ajouter ces comportements en appelant de simples fonctions.

Cela paraît un brin mystérieux, autant donc examiner un exemple simple.

Ajouter un state local avec useState

Voyons tout d’abord comment utiliser les hooks pour ajouter à un composant un état interne :

import React, { useState } from 'react'

const App = () => {
  const [name, setName] = useState('John')
  return (
    <div>
      <p>
        Enter your name:
        <input value={name} onChange={event => setName(event.target.value)} />
      </p>
      <p>Hello {name}!</p>
    </div>
  )
}

Ici notre composant affiche un champ texte contrôlé, c’est-à-dire qu’il définit son attribut value pour indiquer sa valeur, ainsi que l’attribut onChange pour indiquer l’action à effectuer lorsque la valeur est modifiée. Jusque là pour avoir un tel champ de saisie, nous étions obligés de passer soit par un composant déclaré sous forme de classe, soit par une gestion externe de l’état du composant, par exemple avec Redux. Or ici, notre composant dispose d’un état interne, créé grâce à un appel à useState.

useState crée un état interne, prend en paramètre la valeur initial de cet état, et renvoie un tableau dont la première valeur est la valeur actuelle du state, et la seconde valeur est une fonction à appeler pour mettre à jour cet état.

Notez que pour stocker un état complexe il pourrait être tentant de procéder ainsi :

const [state, setState] = useState({ name: '', phone: '', isDone: false })

Mais rien n’interdit d’appeler plusieurs fois useState dans un même composant, donc autant rendre le tout plus lisible :

const [name, setName] = useState('')
const [phone, setPhone] = useState('')
const [isDone, setIsDone] = useState(false)

Mais useState n’est qu’un des hooks proposés par React. Il en existe un autre particulièrement utile : useEffect.

Gérer le cycle de vie du composant avec useEffect

Si useState permet de recréer un état interne au composant, useEffect est son équivalent pour gérer le cycle de vie du composant.

Ajoutons par exemple une fonctionnalité à notre exemple : lorsque la valeur du champ est mise à jour, on souhaite mettre à jour le titre de la page :

import React, { useState, useEffect } from 'react'

const App = () => {
  const [name, setName] = useState('John')

  useEffect(() => {
    document.title = `Hello ${name}!`
  })

  return (
    // ...

useEffect permet de définir une action à effectuer dès que le composant est affiché ou mis à jour. Pour notre cas, cela inclut donc chaque mise à jour de la valeur du champ.

Examinons un autre exemple et réalisons un composant capable d’afficher la taille actuelle de la fenêtre. Il s’agit de s’abonner à l’évènement resize de la fenêtre et de conserver cette taille dans un état interne au composant.

Ici nous nous abonnerons donc à l’évènement dans useEffect, et nous conserverons la taille dans un état déclaré par useState:

const App = () => {
  const [width, setWidth] = useState(0)
  const [height, setHeight] = useState(0)

  const onResize = event => {
    const { innerWidth, innerHeight } = event.target
    setWidth(innerWidth)
    setHeight(innerHeight)
  }

  useEffect(() => {
    setWidth(window.innerWidth)
    setHeight(window.innerHeight)
    window.addEventListener('resize', onResize)
    return () => {
      window.removeEventListener('resize', onResize)
    }
  })

  return (
    <span>
      {width}x{height}
    </span>
  )
}

Notez que cette fois-ci dans la fonction passée à useEffect nous retournons nous-même une fonction. Ainsi nous indiquons à useEffect ce qu’il doit faire lorsque le composant est « démonté », en l’occurrence annuler l’abonnement à l’évènement resize. Finalement, ce que l’on déclare dans useEffect est l’équivalent de la méthode componentDidMount, tandis que la fonction renvoyée est l’équivalent de componentWillUnmount.

Notre composant est relativement élégant, mais est-il possible de sortir la logique de notre composant pour n’y conserver que le rendu ? Bien évidemment, car il est possible de créer nos propres hooks !

Pour notre besoin nous allons créer un hook useWindowSize qui nous permettra d’obtenir la taille actuelle de la fenêtre (en lecture seulement). Notez que par convention les hooks ont un nom préfixé par use :

const useWindowSize = () => {
  const [width, setWidth] = useState(0)
  const [height, setHeight] = useState(0)

  const onResize = event => {/* ... */}

  useEffect(() => { /* ... */ })

  return [width, height]
}

Pour utiliser notre hook, cela se passe comme pour n’importe quel hook que nous avons utilisé jusqu’ici :

const App = () => {
  const [width, height] = useWindowSize()
  return (
    <span>
      {width}x{height}
    </span>
  )
}

Mission accomplie : la logique est à présent dans le hook personnalisé useWindowSize, et notre composant App ne contient plus que l’affichage de la taille de la fenêtre.

Quelques précautions avec les hooks

Les hooks de React peuvent sembler très pratiques et ils le sont, mais également un peu magiques et ils le sont également (je ne parle pas des hooks personnalisés mais bien des hooks proposés par React). En effet, il ne s’agit en réalité pas de fonctions aussi simples qu’elles peuvent le paraître. Elles sont extrêmement liées au noyau de React, et c’est ce qui leur donne tout leur potentiel, mais cela vient avec une contrainte : dans un composant donné, quel que soit le contexte, ce sont toujours les mêmes hooks qui doivent être appelés et dans le même ordre.

Impossible donc d’écrire :

const App = props => {
  if (props.user) {
    useEffect(() => { ... });
  }
  const [user, setUser] = useState({})
  // ...
}

Cela ferait qu’en fonction de la propriété user on appellerait soit useEffect puis useState, soit simplement useState. En fait React gère les hooks grâce à l’ordre dans lequel ils sont appelés dans le composant. Cela paraît contre-intuitif mais c’est un choix qui a ses raisons, et est parfaitement assumé par les équipes développant React.

Enfin si cette contrainte paraît étrange, elle n’est absolument pas dérangeante lorsqu’on utilise les hooks, et il est toujours possible de s’en sortir autrement sans ajouter de complexité.


Les hooks sont encore une fonctionnalité très récente de React mais de toute évidence ils vont vite rentrer dans les pratiques des développeurs. Impossible de ne pas être séduit par l’idée de garder des composants fonctionnels simples. Utiliser des class-components n’est pas une mauvaise pratique loin de là, mais force est de constater qu’un simple composant lorsqu’il est déclaré comme une classe paraît tout de suite plus complexe que son équivalent fonctionnel.

Pour plus d’informations sur les hooks, la documentation de React présente les hooks disponibles et les possibilités offertes à l’aide de nombreux exemples !