3. Consommer une API Rest

Nous avons mis en place une API Rest fonctionnelle sur notre machine locale, mais il nous reste maintenant à utiliser tout cela depuis notre application. Nous allons commencer par récupérer les pokémons que nous affichons dans notre application depuis un serveur distant, plutôt que depuis une simple constante.

Je vous propose de démarrer avec la page qui affiche la liste de tous les pokémons. La route du côté de notre API Rest qui permet de récupérer tous les pokémons est : /pokemons. C’est une requête de récupération de ressources, donc le type de la requête HTTP à envoyer est GET.

Mais la question que nous nous posons, c’est comment faire cette requête ? Qu’est ce qu’il nous faut exactement… Et bien j’ai une bonne nouvelle, nous n’avons besoin de rien ! 😉

Depuis quelques temps, les navigateurs et JavaScript supportent nativement une nouvelle spécification nommée Fetch API. Cette spécification fournit une interface JavaScript pour exécuter et traiter des requêtes HTTP de manière simple. Mais surtout, cette spécification fournit une méthode globale nommée fetch qui permet d’envoyer des requêtes de manière asynchrone. Cela signifie que l’on peut utilisé la méthode fetch() n’importe où dans notre code, sans rien importer. 🔥

De plus, la méthode fetch nous renverra une promesse, ou Promise en anglais. Pour rappel, les promesses sont natives en JavaScript depuis l’arrivée d’ES6, qui permet de faciliter les traitements asynchrones dans votre code. Mais voyons comment tout cela fonctionne.

Dans votre composant pokemon-list.tsx, remplacez le code du Hook d’effet comme ceci :

// Les importations.

const PokemonList: FunctionComponent = () => {
  ...

  useEffect(() => {
    // GET Request
    fetch('http://localhost:3001/pokemons') // On passe une url de récupération des pokémons à fetch().
    .then(response => response.json()) // On reçoit un objet Response de la Fetch API.
    .then((pokemons) => { // On récupère les pokémons grâce à la méthode json() précédente.
      setPokemons(pokemons); // On passe les pokémons à l'état de notre composant.
    });
  }, []);

  return (
    ...
  );
}

export default PokemonList;

Le code qui nous intéresse se trouve de la ligne 8 à 12. En quelques lignes, on récupère des pokémons du serveur et on les place bien au chaud dans le state de notre composant. Mais détaillons point par point.

D’abord à la ligne 8, on effectue une requête de type GET vers l’url http://localhost:3001/pokemons. Comment fait-on cela ? C’est on ne peu plus simple, voici la syntaxe : fetch(url). Et c’est tout !

Ensuite à la ligne 9, on récupère un objet response de la part de notre méthode préférée fetch. Cet objet n’est pas un simple objet JavaScript, mais un objet de type Response spécifiée par la Fetch API. Mais il y a plein d’informations dans cet objet qui ne nous intéresse pas forcément : des en-tête de la réponse HTTP, son statut, ou encore son url, etc. Bref, pour récupérer uniquement ce qui nous intéresse, à savoir les données de nos pokémons, on applique la méthode json sur cette réponse, afin d’extraire ces données.

Enfin, à ligne 20, on a enfin à disposition nos pokémons, et on les place dans le state de notre composant à la ligne 21. Le DOM virtuel s’occupera pour nous d’afficher ces pokémons à nos utilisateurs.

Si vous avez compris ce principe, alors vous êtes prêt à faire de même pour les composants PokemonDetail et PokemonEdit. Il n’y a que la route qui va changer, car pour récupérer les informations d’un seul pokémon, l’url de notre API Rest a appelé est /pokemons/<id>.

Modifier donc le Hook d’effet de notre composant pokemon-detail.tsx comme suit :

// Les importations.

const PokemonsDetail: FunctionComponent<RouteComponentProps<Params>> = ({ match }) => {
  ...

  useEffect(() => {
    // GET Request
    fetch(`http://localhost:3001/pokemons/${match.params.id}`)
    .then(response => response.json())
    .then(pokemon => {
      if(pokemon.id) setPokemon(pokemon);
    });
  }, []);

  return (
    ...
  );
}

export default PokemonsDetail;

Comme vous pouvez le constater, le code de l’envoie de la requête est quasiment identique, mise à part l’url appelée à la ligne 8. Aussi, il y a une petite bizarrerie à la ligne 11. Nous avons mis en place une condition sur l’identifiant du pokémon. Pourquoi une telle vérification… Et bien, c’est dû au fait que l’utilisateur pourrait demander l’identifiant d’un pokémon qui n’existe pas dans notre application, par exemple « 99 ».

Essayez d’accéder à l’url http://localhost:3001/pokemons/99 dans votre navigateur. Vous serez surpris de la réponse de notre API Rest, car vous recevrez un objet vide ! Mais c’est parfaitement normal, car dans notre fichier db.json il n’y a aucun pokémon avec l’identifiant « 99 ». Du coup, notre API Rest nous renvoie un objet vide, qui signifie en fait : Ok, j’ai regardé le pokémon que tu m’as demandé, mais il n’existe pas! Du coup, je te renvoie un objet vide, et tu devras de dérouiller avec ça.

Ce qu’on fait donc à la ligne 11, c’est vérifier si le pokémon reçu depuis le serveur a une propriété « id ». Si c’est le cas, alors on place ce pokémon dans le state de notre composant. Sinon, et bien nous ne faisons rien, car ce n’est pas un pokémon mais un objet vide, et alors notre DOM virtuel affichera « Aucun pokémon à afficher ». Parfait, on gère tous les cas possibles !

Il ne nous reste plus qu’à faire la même chose pour le composant pokemon-edit.tsx :

// Les importations.

const PokemonEdit: FunctionComponent<RouteComponentProps<Params>> = ({ match }) => {
  ...

  useEffect(() => {
    // GET Request
    fetch(`http://localhost:3001/pokemons/${match.params.id}`)
    .then(response => response.json())
    .then(pokemon => {
      if(pokemon.id) setPokemon(pokemon);
    });
  }, []);

  return (
    ...
  );
}

export default PokemonEdit;

Et voilà ! Mine de rien, maitnenant nous récupérons tous nos pokémons depuis notre serveur distant. Et le mieux dans tout ça, c’est que cela ne nous a pas demandé trop de travail non plus. C’est une bonne nouvelle pour la suite !