Il s'agit d'une version statique de l'intranet d'A. Jonquet, certaines fonctionnalités sont donc potentiellement non fonctionnelles.
Rejoindre la version dynamique 🔒
React
Navigation

Une application de partage de signets en React - partie 2

Ce TP est le deuxième d'une série où vous créez la base d'une application permettant d'afficher et de gérer une liste de signets.

Dans le premier TP, vous avez vu comment accéder à une une API à l'aide de l'API fetch.

Ce TP, se focalisera sur la simplification de l'affichage des composants à l'aide d'un routeur : Wouter.

Remarque importante

Les notes des TP seront obtenues à partir de vos dépôts Git, vous devrez donc prendre grand soin de la qualité de ces dépôts et de leurs « commits ».

De manière similaire, les descriptions de vos « commits » devront être claires et informatives, afin de permettre l'évaluation de la progression de votre travail.

Objectifs de la séance

  • Utilisation d'une API de données'
  • Utilisation d'un routeur
  • Gestion de données asynchrones

Ajout du routeur

Comme toujours dans l'environnement React, plusieurs solutions sont envisageables pour mettre en place un routeur dans une application react.

Un paquet largement utilisé par la communauté est React Router, cependant il s'agit d'une bibliothèque supportant de très nombreuses fonctionnalités et cas d'utilisation.

Dans le cadre de ce TP, dans un souci de simplicité et afin de minimiser la dette technique, vous utiliserez une plus petite bibliothèque : Wouter. Vous pouvez installer à l'aide de la commande suivante :

npm install wouter

L'utilisation d'un routeur du coté front-end permet de simplifier la conception et l'organisation du flux applicatif en se concentrant sur l'agencement de vues et leurs relations.

Dans le sous-répertoire « src/components », vous ajouterez un nouveau composant Router, qui contiendra la section de l'application qui sera modifiée au cours de la navigation. Il est possible d'avoir plusieurs sections supportant la navigation dans une application (barre de navigation, menu, ...), mais pour faire simple, nous nous limiterons dans notre cas à la zone d'affichage principale. Dans un premier temps, le composant se contentera d'afficher le composant BookmarkList pour la route « / ». Vous utiliserez Switch pour définir les routes, afin que les routes soient exclusives.

Vous pourrez ensuite utiliser ce routeur dans le composant App en remplacement du composant BookmarkList. Vous devriez maintenant disposer de la même application que précédemment, mais le routeur est maintenant opérationnel.

Travail à réaliser
  • Ajout du paquet Wouter.
  • Création du routeur de l'application.
  • Définition des routes dans App.

Gestion des erreurs

Vous ajouterez un nouveau composant NotFound dans le répertoire « src/views » affichant un message d'erreur de routage avec un lien vers l'accueil de l'application.

Le répertoire « src/views » a pour vocation de contenir des composants représentant les différentes vues de l'application. Idéalement, chaque écran de votre application devrait être représenté par un composant de vue.

Vous ajouterez ensuite une route par défaut affichant le composant NotFound.

Travail à réaliser
  • Création du composant NotFound.
  • Création de la route par défaut affichant une erreur.

La route du détail d'un signet

Pour l'instant l'application affiche une liste de signets. Cependant, il est courant d'avoir besoin d'accéder à une vue détaillée d'un élément de la liste. Vous allez donc ajouter une nouvelle route permettant d'afficher le détail d'un signet, sous la forme « /bookmarks/:id » provoquant l'affichage de la vue Bookmark.

La vue Bookmark, étant rendu par le composant Route, celui-ci injecte une prop params contenant les paramètres de la route (id dans notre cas). Dans un premier temps, vous afficherez la valeur de paramètre pour controller le bon fonctionnement du routeur, par example avec le signet d'identifiant 42.

Pour obtenir le détail d'un signet, vous ajouterez une nouvelle fonction getBookmarkDetail au service bookmarks qui retournera une promesse du JSON décrivant le signet dont l'identifiant est passé en paramètre. Cette fonction détectera les erreurs lors de la requête, à l'aide de la propriété ok de la réponse par exemple, et jettera le status de la réponse comme exception en cas d'erreur.

Dans la vue Bookmark, vous utiliserez le hook useEffect pour invoquer la fonction getBookmarkDetail en fonction du paramètre id. Vous stockerez le résultat de la promesse dans une variable d'état.

Si le résultat de la requête est une erreur, vous réaliserez une redirection vers la route « /not-found ». Cette route n'étant pas définie, devrait afficher une vue d'erreur de ressource non trouvée. Pour réaliser la redirection, vous utiliserez le hook useLocation, vous prendrez soin de remplacer l'historique de navigation afin que l'utilisateur puisse revenir sur la page précédente.

Pendant la requête pour obtenir le détail du signet, vous indiquerez à l'utilisateur le chargement des données à l'aide du composant Loading.

Travail à réaliser
  • Définition de la route « /bookmarks/:id ».
  • Création de la vue Bookmark.
  • Création du service d'obtention du détail d'un signet de l'API.
  • Gestion de la requête AJAX avec indicateur de chargement, redirection en cas d'erreur et affichage du résultat.

Affichage du détail d'un signet

Vous ajouterez un nouveau composant BookmarkDetail au répertoire « src/components », permettant l'affichage du détail d'un signet dont les données seront transmises sous forme de propriété React au composant.

Le composant devra afficher :

  • le nom du signet,
  • le lien vers l'url de destination du signet,
  • la description du signet,
  • la date de création du signet,
  • l'avatar du propriétaire du signet,
  • et la moyenne des notes du signet.

Vous utiliserez ce composant pour afficher le détail du signet obtenu par la requête AJAX dans la vue du détail d'un signet.

Dans votre application, pour permettre à vos utilisateurs d'atteindre le détail d'un signet, vous ajouterez un lien dans le composant BookmarkItem, utilisé dans la liste des signets, permettant de basculer vers la route « /bookmarks/:id » correspondant.

Vous pourrez supprimer le lien de support de l'url du signet et vous ajouterez un lien wouter vers la vue du détail du signet. Conformément à la maquette suivante :

Illustration de l'affichage d'un bookmark
Travail à réaliser
  • Création du composant BookmarkDetail.
  • Affichage du détail d'un signet dans la vue Bookmark.
  • Mise en place de liens vers le détail des signets dans la liste des signets.

Détail d'un utilisateur

De manière similaire au signet, vous allez ajouter une route permettant d'afficher le détail d'un utilisateur, sous la forme « /users/:id » provoquant l'affichage de la vue User.

Cette vue réalisera une requête AJAX pour obtenir les informations de l'utilisateur dont l'identifiant est passé en paramètre dans la route. La vue affichera un indicateur d'activité lors de la requête, redirigera vers la route « /not-found » en cas d'erreur et un composant comportant les nom, prénom, login et avatar de l'utilisateur obtenue par la requête.

Pour réaliser la requête, vous ajouterez une nouvelle fonction getUserDetail au service bookmarks qui retournera une promesse du JSON décrivant l'utilisateur dont l'identifiant est passé en paramètre ou jettera le status de la réponse en cas d'erreur.

Pour finir, vous ferez en sorte que, dans l'application (liste des signets et détail d'un signet, ...), l'avatar des utilisateurs soit clickable et permette de basculer sur la vue détaillée de l'utilisateur associé.

Travail à réaliser
  • Définition de la route « /users/:id ».
  • Création de la vue User.
  • Création du composant UserDetail.

Réusinage du composant BookmarkList

Dans la vue du détail d'un utilisateur, nous souhaitons pouvoir afficher la liste des signets d'un utilisateur. Pour l'instant, le composant BookmarkList affiche la liste des de tous les signets, en réalisant lui-même la requête vers l'API, ce qui empêche la réutilisation du composant dans un autre cas, comme pour afficher les signets d'un utilisateur.

En effet, il s'agit d'une conception non optimale, le composant BookmarkList ne respecte pas le premier principe (« responsabilité unique ») de l'approche SOLID. Ce composant devrait se contenter d'afficher une liste de signets, sans réaliser de requête vers l'API, les signets à afficher lui seront transmis sous forme de propriété React (props).

Vous allez donc réusiner le composant BookmarkList pour qu'il puisse afficher une liste de signets transmis en paramètre sous forme de propriété React (props). La requête sera réalisée dans une nouvelle vue affichant un indicateur d'activité pendant la requête, puis la liste des signets une fois celle-ci résolue.

Le composant BookmarkList recevra maintenant sous forme de props :

  • bookmarksData, la liste des données des signets à afficher,
  • pagination, les informations de pagination. ce paramètre est optionnel et nul par défaut,
  • onPageChange, la fonction de rappel invoquée lorsque le composant de pagination souhaitera changer de page. ce paramètre est optionnel et nul par défaut.

Vous devriez de nouveau avoir votre application fonctionnelle, mais avec une meilleure conception du composant BookmarkList.

Travail à réaliser
  • Réusinage du composant BookmarkList.
  • Création de la vue associée.

Ménage et hook personnalisé

Le composant représentant la vue d'affichage de la liste des signets commence à être un volumineux. En effet, il est conseillé d'essayer de limiter le contenu des composants à du balisage dans un effort de séparation font/forme, en extrayant la logique des composants dans des hooks personnalisés.

Vous allez donc extraire la logique de la vue d'affichage de la liste des signets dans un hook personnalisé usePaginatedBookmark, que vous placerez dans le sous-répertoire « src/hooks » du projet.

Ce hook personnalisé se conformera à la JSDoc suivante, pour gérer la requête et retournera un objet JavaScript contenant :

  • un booléen isLoading indiquant si la requête est en cours,
  • un tableau bookmarksData contenant les données des signets,
  • un objet pagination contenant les informations de pagination,
  • et une fonction de rappel setPage permettant de changer de page.

Vous devriez toujours avoir une application fonctionnelle.

Travail à réaliser
  • Extraction de la logique de la vue d'affichage de la liste des signets.

Ajout de la liste des signets d'un utilisateur

Vous allez maintenant pouvoir ajouter la liste des signets d'un utilisateur dans le détail d'un utilisateur à l'aide du composant BookmarkList.

Le composant UserDetail va devoir réaliser une requête AJAX pour obtenir la liste des signets de l'utilisateur passé en paramètre dans la route, en utilisant une requête sur la collection de signets d'un utilisateur. Cette requête pouvant être paginée, vous devrez aussi gérer la pagination.

Pour simplifier le code du composant, vous réaliserez un nouveau hook personnalisé useUserPaginatedBookmark comportant la logique de requêtage de la liste des signets d'un utilisateur similaire à celui réalisé précédemment.

La vue affichera un indicateur d'activité lors de la requête et la liste des signets obtenue par la requête.

Travail à réaliser
  • Création du hook personnalisé useUserPaginatedBookmark permettant d'obtenir la liste des signets d'un utilisateur et les informations associées.
  • Affichage de la liste des signets d'un utilisateur dans la vue UserDetail.

Conservation de la page courante au cours de la navigation

Vous devriez constater dans votre application que si vous changez de page sur la liste des signets, et qu'ensuite, vous basculez la vue sur le détail d'un signet ou d'un utilisateur, et enfin que vous revenez à la liste des signets à l'aide du bouton de retour, la liste des signets à perdue la page courante et affiche la première page des signets.

Ce comportement s'explique par le fait que la page courante de la collection est stockée dans la vue affichant la liste des signets, et que cette vue est détruite lors de la navigation vers une autre vue. Lorsque la navigation revient sur la liste des signets, une nouvelle vue est créée, et la page courante est de nouveau réinitialisée avec sa valeur par défaut, ici 1.

Il s'agit là d'une expérience utilisateur assès décevante, vous allez donc résoudre ce problème.

Une solution assès simple, consiste à remonter l'information au niveau d'un composant parent qui pérsistera au cours de la navigation. Nous allons aborder une autre approche, vous allez stocker l'information de la page dans la route sous la forme d'une « query string » dans la route.

Lors du changement de page de pagination, vous remplacerez l'historique de navigation pour y ajouter un paramètre d'url page contenant la page courante et vous utiliserez cette variable si elle est fournie pour initialiser la page de la collection lors de la première requête.

Travail à réaliser
  • Sauvegarde de la page courante dans la route listant les signets.
A. Jonquet DUT-INFO/REIMS