Ajouter de l’interactivité

Certaines choses à l’écran se mettent à jour en réponse aux actions de l’utilisateur. Par exemple, en cliquant sur une galerie d’images, l’image active change. En React, les données qui changent au fil du temps sont appelées état. Vous pouvez ajouter un état à n’importe quel composant et le mettre à jour quand nécessaire. Dans ce chapitre, vous apprendrez à écrire des composants qui gèrent des interactions, mettent à jour leur état et ajustent leur affichage au fil du temps.

Réagir aux événements

React vous permet d’ajouter des gestionnaires d’événements à votre JSX. Les gestionnaires d’événements sont vos propres fonctions qui seront déclenchées en réponse aux interactions de l’utilisateur telles que des clics, survols, activations de champs de saisie de formulaires, etc.

Les composants natifs tels que <button> ne prennent en charge que les événements natifs du navigateur tels que onClick. Cependant, vous pouvez également créer vos propres composants et donner à leurs props de gestionnaires d’événements des noms spécifiques à l’application, selon vos besoins.

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Lecture en cours !')}
      onUploadImage={() => alert('Téléversement en cours !')}
    />
  );
}

function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Voir le film
      </Button>
      <Button onClick={onUploadImage}>
        Téléverser une image
      </Button>
    </div>
  );
}

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

Prêt·e à en apprendre davantage ?

Lisez Réagir aux événements pour apprendre comment ajouter des gestionnaires d’événements.

En savoir plus

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

Les composants ont souvent besoin de modifier ce qui est affiché à l’écran en réponse à une interaction. Par exemple, saisir du texte dans un formulaire devrait mettre à jour le champ de saisie, cliquer sur « suivant » dans un carrousel d’images devrait changer l’image affichée, cliquer sur « acheter » ajoute un produit au panier d’achats. Les composants ont besoin de « se souvenir » de certaines choses : la valeur saisie, l’image active, le panier d’achats. En React, ce type de mémoire spécifique au composant est appelé état.

Vous pouvez ajouter un état à un composant avec un Hook useState. Les Hooks sont des fonctions spéciales qui permettent à vos composants d’utiliser des fonctionnalités de React (l’état en est une). Le Hook useState vous permet de déclarer une variable d’état. Il prend l’état initial en argument et renvoie une paire de valeurs : l’état actuel et une fonction qui vous permet de le modifier.

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

Voici comment une galerie d’images utilise et met à jour l’état lors d’un clic :

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

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);
  const hasNext = index < sculptureList.length - 1;

  function handleNextClick() {
    if (hasNext) {
      setIndex(index + 1);
    } else {
      setIndex(0);
    }
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Suivant
      </button>
      <h2>
        <i>{sculpture.name} </i>
        par {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} sur {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Masquer' : 'Afficher'} les détails
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
    </>
  );
}

Prêt·e à en apprendre davantage ?

Lisez L’état : la mémoire d’un composant pour apprendre comment mémoriser une valeur et la mettre à jour lors d’une interaction.

En savoir plus

Rendu et Commit

Avant que vos composants ne soient affichés à l’écran, React doit effectuer leur rendu. Comprendre les étapes de ce processus vous aidera à réfléchir à l’exécution de votre code et à expliquer son comportement.

Imaginez que vos composants soient des cuisiniers dans un restaurant, assemblant des plats savoureux à partir d’ingrédients. Dans ce scénario, React est le serveur qui prend les commandes des clients et leur apporte leurs plats. Ce processus de demande et de service de l’UI comporte trois étapes :

  1. Déclencher un rendu (envoyer la commande du client à la cuisine)
  2. Faire le rendu du composant (préparer la commande en cuisine)
  3. Mettre à jour le DOM (phase de Commit ; revient à déposer la commande sur la table du client)
  1. React agit comme un serveur dans un restaurant, qui récupère les commandes des utilisateurs et les transmet à la cuisine des composants.
    Déclencher
  2. Le chef Card fournit à React un nouveau composant Card.
    Faire le rendu
  3. React dépose le Card sur la table de l’utilisateur.
    Mettre à jour (Commit)

Illustré par Rachel Lee Nabors

Prêt·e à en apprendre davantage ?

Lisez Rendu et Commit pour apprendre sur le cycle de vie d’une mise à jour de l’interface.

En savoir plus

L’état comme un instantané

Contrairement aux variables JavaScript classiques, une variable d’état dans React se comporte davantage comme une photo instantanée. Lui affecter une nouvelle valeur ne change pas la variable d’état que vous avez déjà, mais déclenche plutôt un nouveau rendu. Ça peut surprendre au début !

console.log(count); // 0
setCount(count + 1); // Entraînera un nouveau rendu avec la valeur 1
console.log(count); // Toujours 0 !

Ce comportement vous aide à éviter des bugs subtils. Voici une petite appli de discussion. Essayez de deviner ce qui se passe si vous appuyez sur « Envoyer » d’abord, et ensuite changez le destinataire pour Bob. Quel nom apparaîtra dans le alert cinq secondes plus tard ?

import { useState } from 'react';

export default function Form() {
  const [to, setTo] = useState('Alice');
  const [message, setMessage] = useState('Hello');

  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`Vouz avez dit ${message} à ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        À :{' '}
        <select
          value={to}
          onChange={e => setTo(e.target.value)}>
          <option value="Alice">Alice</option>
          <option value="Bob">Bob</option>
        </select>
      </label>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Envoyer</button>
    </form>
  );
}

Prêt·e à en apprendre davantage ?

Lisez L’état comme un instantané pour comprendre pourquoi un état semble « fixe » et immuable à l’intérieur des gestionnaires d’événements.

En savoir plus

Cumuler les mises à jour d’un même état

Ce composant comporte un bug : cliquer sur « +3 » n’incrémente le score qu’une seule fois.

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(score + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Score: {score}</h1>
    </>
  )
}

L’état comme un instantané en explique la raison. Affecter une nouvelle valeur à un état déclenchera un nouveau rendu, mais ne change pas sa valeur dans le code en cours d’exécution. Ainsi, score reste à 0 juste après avoir appelé setScore(score + 1).

console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0

Vous pouvez corriger ça en passant une fonction de mise à jour lorsque vous affectez une nouvelle valeur à l’état. Voyez comme le remplacement de setScore(score + 1) par setScore(s => s + 1) corrige le bouton « +3 ». Ça vous permet de cumuler plusieurs mises à jour d’un même état.

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(s => s + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Score: {score}</h1>
    </>
  )
}

Prêt·e à en apprendre davantage ?

Lisez Cumuler les série de mises à jour d’un même état pour apprendre comment cumuler plusieurs mises à jour d’une même variable d’état.

En savoir plus

Mettre à jour les objets d’un état

Un état peut contenir n’importe quel type de valeur JavaScript, y compris des objets. Cependant, vous ne devez pas changer directement les objets et les tableaux que vous stockez dans l’état React. Au lieu de cela, lorsque vous voulez mettre à jour un objet ou un tableau, vous devez en créer un nouveau (ou faire une copie de l’existant), puis mettre à jour l’état pour utiliser cette copie.

Généralement, vous utiliserez la syntaxe de spread ... pour copier les objets et les tableaux que vous souhaitez modifier. Par exemple, la mise à jour d’un objet imbriqué pourrait ressembler à ceci :

import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }

  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }

  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }

  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }

  return (
    <>
      <label>
        Nom :
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Titre :
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        Ville :
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image :
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' par '}
        {person.name}
        <br />{person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

Si la copie d’objets dans le code devient fastidieuse, vous pouvez utiliser une bibliothèque telle que Immer pour simplifier le code :

import { useImmer } from 'use-immer';

export default function Form() {
  const [person, updatePerson] = useImmer({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    updatePerson(draft => {
      draft.name = e.target.value;
    });
  }

  function handleTitleChange(e) {
    updatePerson(draft => {
      draft.artwork.title = e.target.value;
    });
  }

  function handleCityChange(e) {
    updatePerson(draft => {
      draft.artwork.city = e.target.value;
    });
  }

  function handleImageChange(e) {
    updatePerson(draft => {
      draft.artwork.image = e.target.value;
    });
  }

  return (
    <>
      <label>
        Nom :
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Titre :
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        Ville :
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image :
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' par '}
        {person.name}
        <br />{person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

Prêt·e à en apprendre davantage ?

Lisez Mettre à jour les objets d’un état pour apprendre comment mettre à jour correctement les objets d’une variable d’état.

En savoir plus

Mettre à jour les tableaux d’un état

Les tableaux sont un autre type d’objet modifiable en JavaScript que vous pouvez stocker dans un état et que vous devez traiter comme étant en lecture seule. Tout comme avec les objets, lorsque vous souhaitez mettre à jour un tableau stocké dans un état, vous devez en créer un nouveau (ou en copier un existant), puis affecter le nouveau tableau dans l’état :

import { useState } from 'react';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [list, setList] = useState(
    initialList
  );

  function handleToggle(artworkId, nextSeen) {
    setList(list.map(artwork => {
      if (artwork.id === artworkId) {
        return { ...artwork, seen: nextSeen };
      } else {
        return artwork;
      }
    }));
  }

  return (
    <>
      <h1>Liste d’œuvres d’art</h1>
      <h2>Ma liste à voir absolument :</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

Si la copie de tableaux dans le code devient fastidieuse, vous pouvez utiliser une bibliothèque telle que Immer pour simplifier le code :

import { useState } from 'react';
import { useImmer } from 'use-immer';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [list, updateList] = useImmer(initialList);

  function handleToggle(artworkId, nextSeen) {
    updateList(draft => {
      const artwork = draft.find(a =>
        a.id === artworkId
      );
      artwork.seen = nextSeen;
    });
  }

  return (
    <>
      <h1>Liste d’œuvres d’art</h1>
      <h2>Ma liste à voir absolument :</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

Prêt·e à en apprendre davantage ?

Lisez Mettre à jour les tableaux d’un état pour apprendre comment mettre à jour correctement les tableaux d’une variable d’état.

En savoir plus

Et maintenant ?

Allez sur Réagir aux événements pour commencer à lire ce chapitre page par page !

Ou alors, si vous êtes déjà à l’aise avec ces sujets, pourquoi ne pas explorer comment Gérer l’état ?