L’état : la mémoire d’un composant

Les composants ont souvent besoin de changer ce qu’ils affichent suite à une interaction. Une saisie dans un formulaire devrait mettre à jour la valeur du champ, cliquer sur « Suivant » sur un carrousel d’images devrait modifier l’image affichée, cliquer sur « Acheter » devrait ajouter le produit au panier. Les composants ont besoin de « se souvenir » de certaines informations : la valeur actuelle du champ, l’image active, le panier. Dans React, ce type de mémoire spécifique au composant est appelée état.

Vous allez apprendre

  • Comment ajouter une variable d’état avec le Hook useState
  • Quelle paire de valeurs le Hook useState renvoie
  • Comment ajouter plus d’une variable d’état
  • Pourquoi on parle d’état local

Quand une variable classique ne suffit pas

Voici un composant qui affiche une image de sculpture. Cliquer sur le bouton « Suivant » derait afficher la sculpture suivante en passant l’index à 1, puis 2 et ainsi de suite. Pourtant, ça ne fonctionne pas (essayez !) :

import { sculptureList } from './data.js';

export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Suivant
      </button>
      <h2>
        <i>{sculpture.name} </i>
        par {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} sur {sculptureList.length})
      </h3>
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

Le gestionnaire d’événement handleClick met à jour une variable locale, index. Mais deux choses empêchent cette modification d’être affichée :

  1. Les variables locales ne persistent pas d’un rendu à l’autre. Lorsque React refait le rendu de ce composant, il recommence à zéro — il ne prend pas en compte les modifications aux variables locales.
  2. Modifier des variables locales ne déclenche pas de rendu. React ne réalise pas qu’il doit refaire le rendu du composant avec les nouvelles données.

Pour mettre à jour un composant avec de nouvelles données, on a besoin de deux choses :

  1. Conserver les données d’un rendu à l’autre.
  2. Déclencher un rendu React du composant avec ces nouvelles données (refaire le rendu).

Le Hook useState remplit ce contrat :

  1. Une variable d’état pour conserver la donnée d’un rendu à l’autre.
  2. Une fonction de mise à jour d’état pour modifier la variable et indiquer à React qu’il doit désormais refaire le rendu du composant.

Ajouter une variable d’état

Pour ajouter une variable d’état, importez useState depuis React en haut de votre fichier :

import { useState } from 'react';

Puis remplacez cette ligne :

let index = 0;

…par celle-ci :

const [index, setIndex] = useState(0);

index est une variable d’état et setIndex est sa fonction de mise à jour.

La syntaxe de crochets [ et ] employée ici s’appelle une déstructuration de tableau (certains traduisent « décomposition », dans un amalgame avec d’autres aspects comme le spread, NdT), elle nous permet de lire plusieurs valeurs depuis un tableau. Le tableau renvoyé par useState contient toujours exactement deux éléments (on parle de « paire »).

Voici comment les utiliser dans handleClick :

function handleClick() {
setIndex(index + 1);
}

À présent, cliquer sur le bouton « Suivant » change bel et bien la sculpture active :

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);

  function handleClick() {
    setIndex(index + 1);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Suivant
      </button>
      <h2>
        <i>{sculpture.name} </i>
        par {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} sur {sculptureList.length})
      </h3>
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

Dites bonjour à votre premier Hook

Dans React, useState est ce qu’on appelle un Hook, au même titre que toute autre fonction dont le nom commence par « use ».

Les Hooks sont des fonctions spéciales qui ne sont utilisables que pendant la phase de rendu de React (on reviendra plus en détails sur ce sujet dans la prochaine page). Ils vous permettent de « vous accrocher » à certaines fonctionnalités de React.

L’état n’est que l’une de ces fonctionnalités, mais vous découvrirez d’autres Hooks prochainement.

Piège

Les Hooks — les fonctions dont le nom commence par use — ne peuvent être appelés que depuis la racine de vos composants ou de vos propres Hooks. Vous ne pouvez pas appeler des Hooks au sein de conditions, de boucles ou de fonctions imbriquées. Les Hooks restent des fonctions, mais il peut être utile de les envisager comme des déclarations inconditionnelles des besoins de votre composant. Vous « utilisez » des fonctionnalités de React à la racine de votre composant, de la même façon que vous « importez » des modules au tout début de votre fichier.

Anatomie de useState

Lorsque vous appelez useState, vous dites à React que vous aimeriez que votre composant se souvienne de quelque chose :

const [index, setIndex] = useState(0);

Dans ce cas précis, vous aimeriez que React se souvienne de index.

Remarque

La convention consiste à nommer la paire quelque chose comme const [something, setSomething]. Vous pouvez les nommer comme bon vous semble, mais les conventions facilitent la compréhension d’un projet à l’autre.

Le seul argument de useState est la valeur initiale de votre variable d’état. Dans cet exemple, la valeur initiale d’index est définie à 0 avec useState(0).

Chaque fois que votre composant fait son rendu, useState vous fournit un tableau contenant deux valeurs :

  1. La variable d’état (index) avec la valeur que vous avez stockée.
  2. La fonction de mise à jour d’état (setIndex) capable de mettre à jour la variable d’état puis de demander à React de refaire le rendu du composant.

Voici comment ça se passe concrètement :

const [index, setIndex