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 d'un context d'application

Assez vite, le routeur peut être encombré par un grand nombre de routes. Afin d'organiser les routes dans le routeur, vous allez regrouper ensemble les routes concernant les signets. Cependant, avant de vous occuper des routes, vous allez simplifier l'utilisation de la route de la liste des signets en déplaçant la page affichée dans un context d'application.

Vous commencerez donc par créer un nouveau contexte React pour l'application.

Ensuite, vous définirez un nouveau fournisseur (« provider ») pour ce contexte, qui contiendra

  • une variable d'état page initialisée à 1.
  • une variable d'état value contenant un object contenant la variable d'état page et sa fonction de modification.
  • un hook d'effet permettant de mettre à jour la variable d'état value lorsque la variable d'état page est modifiée.

La valeur associée au fournisseur de contexte sera bien-sûr value.

Dans le composant App, vous remplacerez la définition de la variable d'état page par la mise en place du contexte de l'application. Et dans la vue de la liste des signets, vous pourrez utiliser la variable page du contexte de l'application pour remplacer la props page.

Aprés avoir nettoyé le code des composants intermédiaire, vous pourrez contrôler que l'application fonctionne toujours correctement.

Travail à réaliser
  • Création du contexte de l'application.
  • Création du fournisseur du contexte de l'application.
  • Utilisation du fournisseur du contexte de l'application dans App.
  • Utilisation du contexte de l'application dans la vue BookmarksList.

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 « bookmark creation »,
  • une vue BookmarkDelete pour la suppression d'un signet, il affichera pour l'instant un message informatif du type « bookmark {id} supression » 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 « bookmark {id} edit » 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 extraire le paramètre de route id, vous pouvez utiliser le hook useParams de Wouter.

Vous allez ensuite ajouter les routes correspondantes à ces vues au routeur de l'application, mais pour organiser vos routes, vous allez créer un nouveau script « bookmarksRoutes.jsx » dans le répertoire « /src/routes ». Ce script exposera une unique fonction bookmarksRoutes dont le role est de fournir les routes relatives aux signets.

Cette fonction

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

Vous utiliserez ensuite cette fonction pour ajouter les routes de gestion des signets au routeur de l'application, vous utiliserez « /bookmarks » comme base pour les routes.

Maintenant que vous disposez d'une route affichant la liste des signets (« /bookmarks »), vous en profiterez pour mettre une redirection sur le chemin de base de l'application (« / »), afin d'éviter la dupliquation de code.

Vous devriez maintenant pouvoir contrôler que les nouvelles routes sont fonctionnelles en y accédant directement en modifiant l'url.

Travail à réaliser
  • Création des vues de gestion des signets.
  • Création des routes de gestion des signets.
  • Ajout des nouvelles routes 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 MenuButton recevant comme propriétés React (props) :

  • icon une icône FontAwesome obligatoire à afficher dans le bouton,
  • to la route vers laquelle rediriger l'application.

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.

Le composant retournera un lien Wouter redirigeant vers la route to. Le support du lien sera un bouton HTML de type button contenant l'icône FontAwesome icon.

Vous allez maintenant 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 Menu recevant comme propriétés React (props) left et right deux tableaux (vides par défaut). Les tableau pourront contenir des objets avec des propriétés permettant de générer des MenuButton : icon et to.

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

Vous générerez des tableaux de MenuButton pour les propriétés React (props) left et right que vous retournerez, encapsulés dans un élément HTML de type nav. Vous pourrez ajouter le balisage nécessaire pour obtenir le rendu souhaité.

Travail à réaliser
  • Création du composant MenuButton.
  • Création du composant Menu.

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 BookmarksList 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 BookmarkDetail 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 BookmarksList vous ajouterez un bouton à gauche permettant de revenir vers le détail du signet.

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.

Vous pourrez alors invoquer la fonction onSubmit en lui transmettant en paramètre les données du signet à créer. 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.

Remarque importante

Il est possible qu'ESLint trouve à redire sur votre gestion des labels dans votre formulaire, comme d'habitude, vous pouvez tout à fait venire corriger les règles de la configuration ESLint pour les adapter à votre code :

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

Création d'un signet

Pour finir, vous allez réaliser la création du signet dans l'API.

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 fetchBookmark 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(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 ajouterez donc une nouvelle propriété React (props) optionnel bookmarkData pour lui transmettre les données du signet à éditer. 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 basculement de l'affichage vers la vue du détail du signet modifié.

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(id, 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(id, data) dans la vue BookmarkCreate.

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(id) 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 basculera l'affichage vers la vue du 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 basculement de l'affichage vers la vue de la liste des signets.
Travail à réaliser
  • Ajout de la fonction deleteBookmark(id) au script src/services/api/bookmarks.js.
  • Ajout de la demande de confirmation pour la suppression dans la vue BookmarkDelete.
  • Utilisation de deleteBookmark(id) dans la vue BookmarkDelete.

Sécurité de l'application

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 ajouterez une nouvelle propriété React (props) hidden au composant MenuButton. Il s'agira d'un booléen et s'il vaut true le composant retourne null sans rien afficher.

Afin d'obtenir la valeur à transmettre, vous allez créer un hook personalisé permettant de savoir si l'utilisateur courant est connecté ou si l'IRI d'une ressource de l'API correspond à l'utilisateur connecté. Vous définirez donc le hook 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,
  • isOwner, 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.

Vous pourrez utiliser ce hook pour définir les valeurs des boutons de navigation qui doivent être supprimés de l'interface en fonction de l'utilisateur :

  • Le bouton de suppression dans la vue d'édition d'un signet suppose que l'utilisateur courant soit le propriétaire du signet,
  • Le bouton de création dans la vue de la liste des signets suppose que l'utilisateur soit connecté,
  • Les boutons d'édition et de suppression de la vue du détail d'un signet supposent l'utilisateur courant soit le propriétaire du signet.

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 utiliser le composant Redirect de Wouter pour sécurisé les vues privées :

  • pour BookmarkCreate, l'utilisateur doit être authentifié,
  • pour BookmarkUpdate et BookmarkDelete, l'utilisateur doit être le propriétaire du signet.
Travail à réaliser
  • Ajout d'une propriété React (props) hidden au composant MenuButton.
  • Utilisation de hidden dans les différentes vues de gestion des signets.
  • Affichage conditionnel des vues de gestion des signets en fonction de l'utilisateur connecté.
A. Jonquet DUT-INFO/REIMS