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.
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.
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 :
BookmarkCreate
pour la création d'un signet, il affichera pour l'instant un message informatif du type « Création de signet »,
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,
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 :
base
correspondant au chemin de base des routes, « /bookmarks » par exemple,
Route
Wouter :
BookmarkCreate
,BookmarkUpdate
,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.
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 :
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é.
NavBtn
.
Nav
.
Vous allez maintenant mettre en place la navigation entre les différentes routes de l'application dans chaque vue.
Bookmarks
vous ajouterez un bouton à droite vers la création d'un signet.
BookmarkCreate
vous ajouterez un bouton à gauche permettant de revenir vers la liste des signets.
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.
BookmarkDelete
vous ajouterez un bouton à gauche permettant de revenir vers le détail du signet.
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.
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,
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 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.
BookmarkForm
.
BookmarkForm
dans la vue BookmarkCreate
.
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 :
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.
postBookmark(data)
au script src/services/api/bookmarks.js.
postBookmark(data)
dans la vue BookmarkCreate
.
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 :
Vous pourrez contrôller que tout fonctionne correctement, notamment que la vue de la création d'un composant est toujours fonctionnel.
patchBookmark(bookmarkId, data)
au script src/services/api/bookmarks.js.
BookmarkForm
.
BookmarkForm
dans la vue BookmarkUpdate
.
patchBookmark(bookmarkId, data)
dans la vue BookmarkUpdate
.
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 :
deleteBookmark(bookmarkId)
au script src/services/api/bookmarks.js.
BookmarkDelete
.
deleteBookmark(bookmarkId)
dans la vue BookmarkDelete
, lors de la validation de la suppression.
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 :
violations
de l'erreur),
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.
useApiError
.Errors
.BookmarkUpdate
, BookmarkCreate
et BookmarkDelete
.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.usePermissions
.