2. Ajouter un nouveau composant

Pour pouvoir mettre en place la navigation dans notre application, nous allons avoir besoin d’au moins deux composants qui affichent une page, afin de pouvoir naviguer d’une page à une autre. Or pour le moment, nous ne disposons que d’un seul composant qui affiche une liste de pokémons. Nous allons donc créer un deuxième composant, qui permettra d’afficher plus de détails d’un pokémon, lorsque l’on cliquera ce pokémon depuis la liste :

La page avec plus de détails sur un pokémon.

Remarquez la nouvelle route que nous allons devoir mettre en place pour accèder à cette page : /pokemons/4. Le chiffre 4 correspond à l’identifiant du pokémon à afficher.

Créeons donc un nouveau composant pokemon-detail.tsx, dans le dossier pages :

import React, { FunctionComponent, useState, useEffect } from 'react';
import { RouteComponentProps, Link } from 'react-router-dom';
import Pokemon from '../models/pokemon';
import POKEMONS from '../models/mock-pokemon';
import formatDate from '../helpers/format-date';
import formatType from '../helpers/format-type';

type Params = { id: string };

const PokemonsDetail: FunctionComponent<RouteComponentProps<Params>> = ({ match }) => {
  
  const [pokemon, setPokemon] = useState<Pokemon|null>(null);

  useEffect(() => {
    POKEMONS.forEach(pokemon => {
      if (match.params.id === pokemon.id.toString()) {
        setPokemon(pokemon);
      }
    })
  }, [match.params.id]);
  
  return (
    <div>
      { pokemon ? (
        <div className="row">
          <div className="col s12 m8 offset-m2"> 
            <h2 className="header center">{ pokemon.name }</h2>
            <div className="card hoverable"> 
              <div className="card-image">
                <img src={pokemon.picture} alt={pokemon.name} style={{width: '250px', margin: '0 auto'}}/>
              </div>
              <div className="card-stacked">
                <div className="card-content">
                  <table className="bordered striped">
                    <tbody>
                      <tr> 
                        <td>Nom</td> 
                        <td><strong>{ pokemon.name }</strong></td> 
                      </tr>
                      <tr> 
                        <td>Points de vie</td> 
                        <td><strong>{ pokemon.hp }</strong></td> 
                      </tr> 
                      <tr> 
                        <td>Dégâts</td> 
                        <td><strong>{ pokemon.cp }</strong></td> 
                      </tr> 
                      <tr> 
                        <td>Types</td> 
                        <td>
                          {pokemon.types.map(type => (
                           <span key={type} className={formatType(type)}>{type}</span>
                          ))}</td> 
                      </tr> 
                      <tr> 
                        <td>Date de création</td> 
                        <td>{formatDate(pokemon.created)}</td> 
                      </tr>
                    </tbody>
                  </table>
                </div>
                <div className="card-action">
                  <Link to="/">Retour</Link>
                </div>
              </div>
            </div>
          </div>
        </div>
      ) : (
        <h4 className="center">Aucun pokémon à afficher !</h4>
      )}
    </div>
  );
}

export default PokemonsDetail;

La plupart du code de ce composant est constitué de code JSX et d’autres concepts de React que nous avons déjà vu. Mais il y a plusieurs nouveautés que je me dois de vous présenter :

  • À la ligne 2, on importe deux éléments de la librairie react-router-dom : RouteComponentProps et Link. Nous allons voir tout de suite à quoi servent ces nouvelles importations.
  • À la ligne 8, on définit un type nommé Params pour l’id que nous récupérerons depuis l’url. Cet identifiant est envoyé sous la forme d’une chaîne de caractères par le routeur de React
  • À la ligne 10, on utilise l’importation RouteComponentProps précédente, pour typer le paramètre reçu depuis le routeur. En effet, ce n’est pas une prop classique que nous injectons dans ce composant, mais une prop issue du routeur, c’est-à-dire une donnée issue de l’url. Ensuite, le routeur de React va associé cette prop issue de l’url, à un objet match qui contient toute les autres données éventuelles passé par le routeur. Dans notre cas, nous avons un unique paramètre id, que nous pouvons maintenant récupérer via match.params.id.
  • À la ligne 12, on définit un state pour sauvegarder le pokémon à afficher. Par défaut, ce state contient la valeur null. La syntaxe Pokemon|null permet de dire à TypeScript : « Ok, cet objet est soit un pokémon OU la valeur nulle« .
  • De la ligne 14 à 20, nous avons un hook d’effet, dans lequel une boucle permet de retrouver le pokémon avec l’identifiant passé en paramètre. Dès que le paramètre de l’url correspond à l’identifiant d’un pokémon dans notre liste, nous l’affections au state du composant, via la méthode setPokemon. Notez que si aucun pokémon n’est trouvé de cette façon, alors pokemon vaut null.
  • La ligne 24 est très importante. On définit un opérateur ternaire, pour vérifier que nous avons bien trouvé un pokémon correspondant au paramètre id de l’url. Si nous avons trouvé un pokémon, nous l’affichons, pas de problème. Sinon, dans le cas contraire, alors nous affichons à l’utilisateur « Aucun pokémon à afficher« . C’est ce que nous faisons à la ligne 70.
  • À la ligne 52 et 57, on réutilise les méthodes formatDate et formatType que nous avons factoriser précédemment, car nous avons la même logique d’affichage à mettre en place dans le template de notre composant concernant les dates et les types.
  • Enfin, à la ligne 63, on utilise l’élément Link, qui permet de générer un lien. Il faut lui passer une valeur correspondant au chemin vers lequel on souhaite rediriger l’utilisateur, via la prop nommée to. Dans notre cas, on veut rediriger la personne vers la liste des pokémons, qui se trouve être la page d’accueil de notre application pour le moment, et donc on lui passe l’url vide « / ».

Voilà, je pense que vous avez toutes les explications nécessaires pour comprendre le code de ce composant. Prenez le temps de vous familiariser avec ce composant, et prochainement nous allons le raccorder avec le reste de notre application grâce à un système de route.

Pour le moment, notre composant ne fait rien, car il n’est pas relié au routeur de la librairie react-router-dom. Il n’est pas associer à une route, et est donc inaccessible depuis notre interface actuelle. Cependant, plus tard, lorsque nous naviguerons sur l’url : /pokemons/4, ce composant affichera le pokémon avec l’identifiant n°4.