Contourner les Rules of Hooks : Getter Functions

Contourner les Rules of Hooks : Getter Functions

Dans l'article précédent, je montrais le pattern Getter Components (un nom totalement inventé) pour contourner les Rules of Hooks. Pratique dans le cas où vous ne pouvez pas appeler de hooks sans respecter leurs règles.

Mais ce n'est pas la seule manière de les contourner ! J'utilise régulièrement un seconde astuce : retourner un getter plutôt que la valeur.

Reprenons l'exemple du post précédent. Nous avions :

  • un hook usePicture(id: string): Picture
  • un component ArticleList qui doit afficher une liste de Article
  • et pour chaque Article, ce component affiche une image si l'attribut coverPictureId du Article n'est pas null

Soit, en code :

type Article = {
  id: string
  title: string
  coverPictureId: string | null
}

type Picture = {
  id: string
  src: string
}

declare function usePicture(id: string): Picture

function ArticleList({ articles } { articles: Article[] }) {
  return (
    <ol>
      {articles.map(article => (
        <li key={article.id}>
          {article.coverPictureId && (
            <img src={/* TODO: obtenir le bon src ici */} />
          )}
          {article.title}
        </li>
      )}
    </ol>
  )
}

Le problème est le même que dans l'article précédent : notre img étant dans une boucle (.map) et un conditionnel (&&), impossible d'utiliser notre hook usePicture pour obtenir le bon src.

En fait, une solution plutôt élégante est de changer la signature de notre hook pour qu'il ne retourne plus une valeur, mais une fonction permettant d'obtenir une valeur.

Un getter quoi.

Donc, usePicture(id: string): Picture peut devenir useGetPicture(): (id: string) => Picture.

Et il devient ainsi possible de rendre l'appel au hook inconditionnel, mais d'utiliser la fonction dans l'itération ou le conditionnel tout en respectant les règles des hooks.

Notre component devient alors :

function ArticleList({ articles } { articles: Article[] }) {
  const getPicture = useGetPicture()
  return (
    <ol>
      {articles.map(article => (
        <li key={article.id}>
          {article.coverPictureId && (
            <img src={getPicture(article.coverPictureId).src} />
          )}
          {article.title}
        </li>
      )}
    </ol>
  )
}

✅ Simple
✅ Basique

À noter : c'est mon go-to pour contourner les règles des hooks, mais cette solution a une grosse contrainte. Elle est "contaminante". Par exemple, elle n'est pas compatible avec React Query puisqu'il est impossible de fetcher une query dans une fonction.

Pour ces cas là, j'utilise donc toujours les Getter Components.

Hapy coding ✌️