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 BookmarkCreate pour la création d'un signet, il affichera pour l'instant un message informatif du type « Création de signet »,
  • une vue BookmarkDelete 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 BookmarkUpdate 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 Bookmark, 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 :

  • recevra un paramètre base correspondant au chemin de base des routes, « /bookmarks » par exemple,
  • et retournera un tableau de Route Wouter :
    • « /bookmarks/create » affichant la vue BookmarkCreate,
    • « /bookmarks/:id/update » affichant la vue BookmarkUpdate,
    • « /bookmarks/:id/delete » affichant la vue BookmarkDelete.

L'ordre de déclaration des routes est importante, le routeur affichera la vue de la première route correspondante à la route actuelle.

Vous devriez maintenant pouvoir contrôler que les nouvelles routes sont fonctionnelles en y accédant directement en modifiant directement la route dans 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'URL 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 NavBtn 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.

La définition de la PropType de icon sera un objet JavaScript contenant les propriétés prefix et iconName de type string et isRequired.

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 NavBtn à 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 NavBtn.
  • 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 Bookmarks vous ajouterez un bouton à droite vers la création d'un signet.
  • Dans la vue BookmarkCreate vous ajouterez un bouton à gauche permettant de revenir vers la liste des signets.
  • Dans la vue Bookmark 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 BookmarkDelete vous ajouterez un bouton à gauche permettant de revenir vers le détail du signet.
  • Dans la vue BookmarksUpdate 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 navigation du routeur, 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.

Pour commencer, vous créerez un nouveau composant BookmarkForm qui affichera un formulaire HTML permettant de saisir les données d'un signet.

Le composant recevra comme propriété React (props) :

  • title le titre du formulaire,
  • submit le texte du bouton de soumission,
  • et onSubmit la fonction à invoquer lors de la soumission du formulaire.

Les informations minimales nécessaires à l'API pour créer un signet sont un nom, une URL, une description et un attribut rendant le signet public ou privé.

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. Par souci d'optimisation des rendus de l'application, vous utiliserez le hook de React useCallback pour mémoïser la fonction de rappel de soumission du formulaire.

Dans cette fonction mémoïsée, 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 BookmarkCreate.

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/bookmarks.js », permettant la création d'un signet.

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 BookmarkCreate, 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.

Lorsque le signet créé est privé, il n'est pas accessible aux autres utilisateurs et vous ne pouvez voir vos signets privés que si vous êtes authentifié. Vous veillerez donc à contrôler que vos requêtes fetchAllBookmarks et getBookmarkDetail fournissent bien votre cookie de session afin d'obtenir les signets privés aussi.

Travail à réaliser
  • Ajout de la fonction postBookmark(data) au script src/services/api/bookmarks.js.
  • Utilisation de postBookmark(data) dans la vue BookmarkCreate.
  • 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/bookmarks.js, permettant l'édition d'un signet.

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 BookmarkUpdate, de manière similaire à la vue BookmarkDetail, 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 le retour à la vue précédente, le 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/bookmarks.js.
  • Ajout d'une props permettant d'initialiser le composant BookmarkForm.
  • Initialisation de BookmarkForm dans la vue BookmarkUpdate.
  • Utilisation de patchBookmark(bookmarkId, data) dans la vue BookmarkUpdate.

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/bookmarks.js, permettant la suppression d'un signet dans l'API.

Vous ajouterez ensuite à la vue BookmarkDelete 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 détail du signet.

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 vue précédente, le détail du signet.
Travail à réaliser
  • Ajout de la fonction deleteBookmark(bookmarkId) au script src/services/api/bookmarks.js.
  • Ajout de la demande de confirmation pour la suppression dans la vue BookmarkDelete.
  • Utilisation de deleteBookmark(bookmarkId) dans la vue BookmarkDelete, 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.

Pour commencer, vous allez 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/bookmarks.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 ou 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 en suite cette fonction lors des requêtes postBookmark, patchBookmark et deleteBookmark.

Pour gérer les messages d'erreur, afin de mutualiser le code, vous créerez un nouveau hook personnalisé useApiError. 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 et retournera un objet JavaScript contenant deux propriétés :

  • errors, contenant les erreurs à afficher,
  • setApiError, une fonction permettant de remplir la liste des erreurs à afficher.

Vous utiliserez le hook useCallback pour créer la fonction setApiError. Cette fonction recevra en paramètre optionnel l'erreur de l'API et en conséquence remplira la liste des erreurs à afficher :

  • sans paramètre, le tableau des erreurs est vidé,
  • 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é detail 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 BookmarkUpdate, BookmarkCreate et BookmarkDelete, 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 des erreurs de l'API dans les vues BookmarkUpdate, BookmarkCreate et BookmarkDelete.

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.

Pour commencer, vous allez cacher les boutons n'ayant pas 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 propriétaire de ressource de l'API et qui retournera un booléen définissant si cet IRI 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. Il est cependant possible que vous deviez modifier le service pour qu'il retourne une valeur entière.

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

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 :

  • BookmarkCreate lorsque l'utilisateur n'est pas authentifié,
  • BookmarkUpdate et BookmarkDelete 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é.
A. Jonquet DUT-INFO/REIMS