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 4

Ce TP est le dernier 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 les TP précédents, vous avez vu comment accéder à une API à l'aide de l'API fetch, mettre en place un routage coté client à l'aide de Wouter et gérer des données globales pour l'application à l'aide des contextes React, notamment pour gérer l'utilisateur connecté.

Ce dernier TP, se focalisera sur l'édition de données et la mise en place de formulaires.

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 des contextes React
  • Utilisation d'un routeur
  • Gestion de données asynchrones
  • Gestion de formulaires

Ajout des routes d'interaction avec les signets

Vous allez maintenant ajouter et regrouper les routes permettant d'interaction avec les signets.

Pour commencer, vous allez créer trois vues pour les signets :

  • une vue BookmarkCreateView pour la création d'un signet, il affichera pour l'instant un message informatif du type « Création de signet »,
  • une vue BookmarkDeleteView pour la suppression d'un signet, il affichera pour l'instant un message informatif du type « Suppression du signet {id} » où {id} est l'identifiant du signet à supprimer transmis comme paramètre de route,
  • et une vue BookmarkUpdateView pour l'édition d'un signet, il affichera pour l'instant un message informatif du type « Édition du signet {id} » où {id} est l'identifiant du signet à éditer transmis comme paramètre de route.

Comme nous l'avons vu dans le TP précédent pour la vue BookmarkView, pour obtenir le paramètre de route id, vous pouvez utiliser la propriété params injecter par le composant Route de wouter.

Vous pourrez ensuite ajouter les routes correspondantes à ces vues au routeur de l'application :

  • « /bookmarks/create » affichant la vue BookmarkCreateView,
  • « /bookmarks/:id/update » affichant la vue BookmarkUpdateView,
  • « /bookmarks/:id/delete » affichant la vue BookmarkDeleteView.

Attention, l'ordre de déclaration des routes est importante, le routeur affichera la vue de la première route correspondante à la route actuelle, vous devrez peut-être réorganiser vos routes pour qu'elles soient toutes fonctionnelles.

Vous devriez maintenant pouvoir contrôler que les nouvelles routes sont fonctionnelles en y accédant directement en modifiant la route dans la barre d'adresse de votre navigateur.

Travail à réaliser
  • Création des vues de gestion des signets.
  • Ajout des routes de gestion des signets au routeur de l'application.

Création d'un menu de navigation

La navigation entre les différentes routes, en modifiant l'adresse de la page, est un peu fastidieuse. Vous allez créer un composant permettant de définir un menu de navigation. Le menu devra être construit spécifiquement pour chaque vue, l'objectif sera donc de simplifier la création de ce menu de navigation.

Vous commencerez par créer un composant NavButton recevant comme propriétés React (props) :

  • icon une icône SVG FontAwesome obligatoire à afficher dans le bouton,
  • to une route optionnelle vers laquelle rediriger l'application, null par défaut.
  • onClick une fonction de rappel optionnelle, invoquée lors du clic de la souris sur le bouton, null par défaut.

Lorsque la propriété React to est définie, le composant retournera un lien Wouter redirigeant vers la route to, sinon le bouton sera un bouton HTML de type button, chacun contenant l'icône FontAwesome icon.

Le composant NavButton répondra à la JsDoc suivante :

Vous pourrez ensuite créer le menu de navigation, sous la forme d'une barre de navigation avec deux zones de boutons optionnelles, à gauche et à droite, comme illustré dans la maquette suivante :

Illustration du menu de navigation

Vous définirez donc un composant Nav recevant comme propriétés React (props) left et right deux tableaux optionnels (vides par défaut). Ces tableaux contiendront les composants NavButton à afficher.

Un exemple de construction de menu pourrait être le suivant :

Vous pourrez ajouter le balisage nécessaire pour obtenir le rendu souhaité.

Travail à réaliser
  • Création du composant NavButton.
  • Création du composant Nav.

Ajout de la navigation

Vous allez maintenant mettre en place la navigation entre les différentes routes de l'application dans chaque vue.

  • Dans la vue BookmarksView vous ajouterez un bouton à droite vers la création d'un signet.
  • Dans la vue BookmarkCreateView vous ajouterez un bouton à gauche permettant de revenir vers la liste des signets.
  • Dans la vue BookmarkView vous ajouterez un bouton à gauche permettant de revenir vers la liste des signets et deux boutons à droite vers la modification et la suppression du signet.
  • Dans la vue BookmarkDeleteView vous ajouterez un bouton à gauche permettant de revenir vers le détail du signet.
  • Dans la vue BookmarksUpdateView vous ajouterez un bouton à gauche permettant de revenir vers le détail du signet.

Pour le bouton permettant de revenir vers la liste des signets, vous n'utiliserez pas la navigation du routeur. Vous préférez utiliser la méthode back de la classe History du navigateur, qui permettra de simuler un clic sur le bouton retour du navigateur. Ainsi, la pagination stockée sous forme de paramètre d'URL sera utilisé pour rétablir l'interface précédente.

Tous les autres boutons utiliseront la propriété de navigation to du composant NavButton, pour provoquer la navigation dans l'application.

Vous devriez maintenant pouvoir contrôler que les routes sont toujours fonctionnelles en passant par l'interface.

Travail à réaliser
  • Ajout des menus de navigation spécifique aux 5 vues de gestion des signets.

Un formulaire de création d'un signet

Vous allez maintenant permettre à l'utilisateur de créer un nouveau signet.

Dans cette optique, vous allez devoir créer un composant proposant permettant d'obtenir les données du signet à créer. Vous commencerez par ajouter au fichier « types/Types.d.ts » la définition de type pour l'objet produit par le formulaire :

Vous créerez ensuite un nouveau composant BookmarkForm qui affichera un formulaire HTML permettant de saisir les données d'un signet.

Le composant NavButton correspondra à la JsDoc suivante :

Les informations minimales nécessaires à l'API pour créer un signet sont un nom, une URL et une description, soit les propriétés définies dans le type BookmarkData. Vous produirez donc un formulaire permettant à l'utilisateur de définir ces propriétés.

React propose deux approches pour gérer les formulaires :

  • la version contrôlée, favorisée par React, permet de toujours avoir accès aux données du formulaire, au prix d'une complexité et d'une charge de rendu accrues,
  • la version non-contrôlée, plus proche du web classique, est plus simple, mais ne donne accès aux données que lors d'événements émis par DOM, comme la soumission du formulaire.

La documentation de la seconde approche vous montre une solution reposant sur la propriété target de l'événement onSubmit. C'est cette solution que vous allez employer en ajoutant un gestionnaire d'événements sur la soumission du formulaire, vous permettant d'accéder aux éléments du formulaire grâce à l'événement transmis en paramètre.

Dans la fonction de rappel de soumission du formulaire, vous pourrez invoquer la fonction onSubmit en lui transmettant en paramètre les données du signet à créer, issues du formulaire. Vous veillerez à prévenir le comportement par défaut de l'événement pour empêcher le rechargement de la page.

Pour finir, vous utiliserez ce composant dans la vue de la création d'un signet en transmettant la fonction console.log comme props onSubmit. Vous devriez maintenant pouvoir tester votre formulaire et obtenir l'affichage des données dans la console de développement.

Travail à réaliser
  • Création du composant BookmarkForm.
  • Utilisation de BookmarkForm dans la vue BookmarkCreateView.

Création d'un signet

Pour finaliser la création du signet, vous allez réaliser la requête de création du signet dans l'API lors de la soumission du formulaire.

Vous commencerez par ajouter un nouveau service d'interaction avec l'API sous la forme d'une fonction postBookmark(data) au script « src/services/api/bookmarksApi.js », permettant la création d'un signet, conformément à la JsDoc suivante :

Cette interaction doit se faire en étant authentifié, vous penserez donc bien à transmettre votre cookie de session lors de vos requêtes. Vous pourrez utiliser la méthode de classe JSON.stringify() pour générer le contenu de la requête. Et le type mime de la requête pour le contenu transmis à l'API doit être : « application/ld+json ».

Pour finir, dans la vue BookmarkCreateView, vous ferez en sorte que la soumission du formulaire :

  • change l'affichage pour afficher un message d'information indiquant que le signet est en cours de création.
  • réalise la requête AJAX pour créer le signet dans l'API.
  • puis lorsque la requête répond, provoque le basculement de l'affichage vers la vue du détail du signet créé. Le plus simple étant probablement d'utiliser le hook useLocation de Wouter.

Pour indiquer la création du signet lors de la requête, une solution simple peut être de rendre le texte du composant Loading paramétrable et de l'utiliser pour signifier l'activité de l'application.

Les signets créés par un utilisateur sont privés, vous ne pourrez donc voir ces signets que si vous êtes authentifié. Vous veillerez donc à contrôler que vos requêtes getAllBookmarks, getBookmarkDetail et getUserBookmarks fournissent bien votre cookie de session afin d'obtenir aussi les signets privés de l'utilisateur.

Travail à réaliser
  • Ajout de la fonction postBookmark(data) au script src/services/api/bookmarksApi.js.
  • Utilisation de postBookmark(data) dans la vue BookmarkCreateView.
  • Indicateur d'activité lors de la requête.
  • Contrôle de la récupération des signets privés.

Édition d'un signet

Sur le même principe, pour éditer un signet, vous ajouterez une fonction patchBookmark(bookmarkId, data) au script src/services/api/bookmarksApi.js, permettant l'édition d'un signet. Pour le typage du paramètre data, vous utiliserez le type générique Partial de TypeScript avec le type BookmarkData.

Le composant BookmarkForm que vous avez créé précédemment, pour la création d'un signet, pourra être réutilisé pour l'édition d'un signet. Vous lui ajouterez une nouvelle propriété React (props) optionnel bookmarkData pour lui transmettre les données du signet à éditer. Et vous utiliserez ces données pour initialiser les champs du formulaire lorsqu'elles sont présentes.

Dans la vue BookmarkUpdateView, de manière similaire à la vue BookmarkView, vous réaliserez une requête permettant d'obtenir les données du signet à éditer. Vous utiliserez ces données pour initialiser le formulaire de modification du signet.

Enfin, pour finir, lors de la soumission du formulaire, vous ferez en sorte que le composant :

  • change l'affichage pour afficher un message d'information indiquant que le signet est en cours de mis-à-jour.
  • réalise la requête AJAX pour modifier le signet dans l'API.
  • puis lorsque la requête répond, provoque l'affichage du détail du signet.

Vous pourrez contrôller que tout fonctionne correctement, notamment que la vue de la création d'un composant est toujours fonctionnel.

Travail à réaliser
  • Ajout de la fonction patchBookmark(bookmarkId, data) au script src/services/api/bookmarksApi.js.
  • Ajout d'une props permettant d'initialiser le composant BookmarkForm.
  • Initialisation de BookmarkForm dans la vue BookmarkUpdateView.
  • Utilisation de patchBookmark(bookmarkId, data) dans la vue BookmarkUpdateView.

Suppression d'un signet

Pour finir les interactions avec l'API, vous allez ajouter la suppression d'un signet. Vous commencerez par ajouter une fonction deleteBookmark(bookmarkId) au script src/services/api/bookmarksApi.js, permettant la suppression d'un signet dans l'API.

Vous ajouterez ensuite à la vue BookmarkDeleteView un message demandant la confirmation pour la suppression du signet, ainsi que deux boutons ; pour annuler ou confirmer la suppression.

Le bouton d'annulation provoque le retour à la vue précédente.

Le bouton de confirmation :

  • change l'affichage pour afficher un message d'information indiquant que le signet est en cours de suppression.
  • réalise la requête AJAX pour supprimer le signet dans l'API.
  • puis lorsque la requête répond, provoque le retour à la liste des signets.
Travail à réaliser
  • Ajout de la fonction deleteBookmark(bookmarkId) au script src/services/api/bookmarksApi.js.
  • Ajout de la demande de confirmation pour la suppression dans la vue BookmarkDeleteView.
  • Utilisation de deleteBookmark(bookmarkId) dans la vue BookmarkDeleteView, lors de la validation de la suppression.

Gestion des erreurs de l'API

Vous avez dû constater que les interactions avec l'API peuvent produire des erreurs. Des exemples faciles peuvent être la modification d'un signet ne vous appartenant pas ou la modification d'un signet vous appartenant, mais avec des valeurs non-valides. Vous allez donc voir comment gérer ces erreurs.

Vous commencerez par ajouter un nouveau type au fichier types/Api.d.ts pour gérer les erreur produite par l'API :

Ensuite, pour détecter et informer l'application de ces erreurs lors des requêtes postBookmark, patchBookmark et deleteBookmark. Vous ajouterez la fonction suivante au script src/services/api/bookmarksApi.js :

Cette fonction reçoit en paramètre une promesse de réponse de l'API fetch et retourne cette même réponse. Mais dans le cas où la réponse n'a pas été fructueuse, elle ajoute une promesse de jet d'exception contenant l'erreur produite en JSON par l'API.

Vous utiliserez ensuite cette fonction lors des requêtes postBookmark, patchBookmark et deleteBookmark.

Pour gérer les messages d'erreur et afin de mutualiser le code, vous créerez un nouveau hook personnalisé useApiError dont la JsDoc pourrez être :

Ce hook ne recevra pas de paramètre, définira une variable d'état errors contenant les erreurs à afficher sous la forme d'un tableau de chaîne de caractères, vide par défaut, et retournera un objet JavaScript contenant trois propriétés :

  • errors, contenant les erreurs à afficher,
  • processApiError, une fonction permettant de remplir la liste des erreurs à afficher à partir d'une erreur de l'API,
  • clearErrors, une fonction permettant de vider la liste des erreurs à afficher.

La fonction processApiError recevra en paramètre l'erreur de l'API jetée par les services de requêtage et remplira la liste des erreurs à afficher :

  • si l'erreur n'a pas de status, un message d'erreur critique,
  • si le status de l'erreur est 422, le tableau des erreurs est rempli avec les erreurs de validation de l'API (propriété violations de l'erreur),
  • si le status de l'erreur est 401, le tableau contient un message indiquant que l'action n'est pas permise,
  • dans les autres cas, le tableau contient un message générique pouvant contenir un indice sur l'erreur (propriété status de l'erreur).

Pour afficher les erreurs, vous créerez un nouveau composant Errors qui recevra en paramètre un tableau de chaîne de caractères des erreurs à afficher. Son rôle sera de réaliser l'affichage de ces erreurs.

Dans les vues BookmarkUpdateView, BookmarkCreateView et BookmarkDeleteView, vous utiliserez les exceptions des requêtes vers l'API pour obtenir les erreurs et le hook personnalisé useApiError et le composant Errors pour en informer l'utilisateur.

Travail à réaliser
  • Ajout de la gestion des erreurs lors des requêtes vers l'API.
  • Création du hook personnalisé useApiError.
  • Création du composant Errors.
  • Gestion et affichage des erreurs de l'API dans les vues BookmarkUpdateView, BookmarkCreateView et BookmarkDeleteView.

Sécurité et UI/UX

L'application est fonctionnelle et la sécurité des données est assurée par l'API. Cependant, l'expérience utilisateur n'est pas optimale sans gestion des rôles des utilisateurs. Par exemple, un utilisateur anonyme ne devrait pas avoir accès à la zone de création d'un signet. Ni un utilisateur accès à la modification d'un signet ne lui appartenant pas.

Vous commencerez par cacher les boutons n'ayant pas de sens dans l'interface. Vous commencerez par créer un nouveau hook personnalisé usePermissions qui retournera un object JavaScript comportant 2 propriétés :

  • isAuthenticated, contenant un booléen définissant si un utilisateur est définie dans le context de l'utilisateur courant,
  • isUser, contenant une fonction recevant l'IRI d'un utilisateur et retournant un booléen définissant si cet utilisateur correspond à l'utilisateur courant.

Pour contrôller que l'utilisateur en paramètre identifie l'utilisateur courant, vous pourrez utiliser le service de transformation iriToId pour le comparer à l'identifiant de l'utilisateur du context.

Vous pourrez ensuite utiliser ce hook personnalisé pour ajouter de manière conditionnelle les boutons de navigation de création, suppression et édition dans les différentes vues.

Maintenant votre interface est plus cohérent, mais même si les routes ne sont pas accessibles par l'interface, elles sont tout de même disponibles par l'URL, ce qui peut amener à des comportements contre-intuitifs de l'application, notamment lors de la déconnexion de l'utilisateur. Vous allez donc réaliser, dans les vues suivantes, une redirection vers la page précédente :

  • BookmarkCreateView lorsque l'utilisateur n'est pas authentifié,
  • BookmarkUpdateView et BookmarkDeleteView lorsque l'utilisateur n'est pas le propriétaire.

Travail à réaliser
  • Création du hook personnalisé usePermissions.
  • Affichage conditionnel des boutons de navigation de création, suppression et édition.
  • Redirection des vues en fonction de l'utilisateur connecté.

Persistance des données du formulaire

Pour finir, vous avez dû constater que lorsque l'utilisateur édite ou créer un nouveau signet et qu'une erreur survient, par example en soumettant une URL mal formatée, alors le formulaire est réinitialisé, perdant toutes les modifications de l'utilisateur. Il s'agit encore d'une mauvaise experience utilisateur.

Pour résoudre ce problème, vous veillerez à sauver les données du formulaire lors de la soumission dans les vues BookmarkCreateView et BookmarkUpdateView afin qu'elles puissent être réinjecter dans le formulaire lors de la création du nouveau formulaire aprés l'erreur de l'API.

Dans le cas de BookmarkUpdateView, il s'agit principalement de mettre à jour les données du formulaire lors de la soumission. Mais dans le cas BookmarkCreateView, vous devrez ajouter un nouvelle état pour stocker ces informations et les transmettre au formulaire.

Travail à réaliser
  • Persistance des données du formulaire dans les vues BookmarkCreateView et BookmarkUpdateView lors des erreurs.
A. Jonquet DUT-INFO/REIMS