Ce TP est le troisième d'une série où vous allez créer la base d'une application permettant d'afficher et de gérer une liste de signets.
Il se focalisera sur la simplification de la gestion des données des composants à l'aide des contextes React.
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.
Pour authentifier l'utilisateur de l'application auprès de l'API vous allez utiliser les cookies de session. Et pour obtenir les informations de l'utilisateur courant, vous utiliserez la ressource « /me » de l'API.
Vous ajouterez une nouvelle fonction getMe()
au module « services/api/bookmarks.js » qui retournera une promesse des données de l'utilisateur si celui-ci est connecté et une promesse de null
sinon. La requête obtient un code de réponse HTTP 401 si l'utilisateur n'est pas connecté.
Pour informer le navigateur qu'il doit transmettre le cookie de session lors de la requête AJAX, vous utiliserez l'option credentials
de fetch
avec la valeur "include"
.
Vous en profiterez pour ajouter deux nouvelles fonctions, exportées nommées :
loginUrl()
retournant l'URL de connexion de l'API en utilisant la constante BASE_URL
logoutUrl()
retournant l'URL de déconnexion de l'API en utilisant la constante BASE_URL
.
L'URL de connexion de l'API peut être complétée avec un paramètre d'URL redirect
permettant à l'utilisateur d'être redirigé vers l'application après son authentification. Le paramètre doit recevoir l'URL vers laquelle réaliser la redirection. Pour obtenir l'URL courante du navigateur, vous pouvez utiliser la propriété location
de l'objet window
.
Vous penserez à protéger la valeur du paramètre à l'aide de la fonction encodeURIComponent
.
getMe()
.
loginUrl()
.
logoutUrl()
.
L'application va devoir stocker les informations de l'utilisateur connecté et les rendre disponibles à l'ensemble de l'application.
Pour éviter de transmettre cette information au travers des composants de l'application en la transmettant sous forme de nombreuses propriétés de composants, ce qui deviendrait rapidement fastidieux, vous allez les stocker dans un contexte React.
Vous commencerez par créer le contexte. Dans un sous-répertoire « src/contexts » vous ajouterez un nouveau script « user.jsx » dont le rôle principal sera d'exporter par défaut le contexte de l'utilisateur courant.
Vous ajouterez à ce script un export nommé UserProvider
exportant le fournisseur (« provider ») de contexte pour l'utilisateur courant. Pour ce faire, vous exporterez un composant React d'ordre supérieur UserProvider
dont le rendu sera composé du fournisseur (« provider ») de contexte de l'utilisateur, encapsulant les fils du composant.
Le composant UserProvider
comportera une variable d'état userData
qui stockera les données de l'utilisateur. Cette variable pourra être dans l'un de ces 3 états :
undefined
,
null
.
La valeur par défaut de userData
sera donc undefined
jusqu'à ce que le composant obtienne une réponse HTTP de la fonction getMe
, émise depuis un hook useEffect
.
La valeur du contexte initialisée par le fournisseur (« provider ») de contexte contiendra la variable d'état userData
.
Vous rendrez le contexte de l'utilisateur accessible à tous les composants de l'application en utilisant le composant UserProvider
, fournissant le contexte, dans le fichier « App.jsx ». Vous devrez constater dans votre console de développement l'exécution de la requête AJAX vers getMe
.
ESLint devrait vous faire une erreur sur l'export par défaut du context. En effet,
Pour illustrer l'absence de propagation de propriété de composant, vous allez créer un composant Header
pour l'en-tête de l'application, dont le seul rôle sera d'afficher le titre de l'application et de contenir le bouton de gestion de l'utilisateur.
Le composant recevra une propriété de composant title
contenant le titre de l'application.
Vous utiliserez ensuite ce composant dans le composant App
afin qu'il apparaisse dans votre application, et vous devriez de nouveau obtenir une application fonctionnelle.
Header
.
Header
dans le composant App
.
Maintenant que les données de l'utilisateur sont disponibles, vous allez créer un nouveau composant UserButton
permettant à l'utilisateur de savoir s'il est connecté ou non et de se connecter ou déconnecter selon le cas.
Vous ajouterez le composant React UserButton
à votre projet et il utilisera le contexte de l'utilisateur pour obtenir ses données, en utilisant le hook useContext
.
Selon la valeur des données de l'utilisateur, le composant affichera :
undefined
.
null
.
L'indicateur de chargement pourra réutiliser votre composant Loading
créé dans le TP précédent, éventuelement en lui ajoutant une propriété restreignant son affichage à une icône.
Le bouton de connexion sera un simple lien vers l'URL de connexion de l'API web (« /login »).
Le bouton de déconnexion sera aussi un simple lien vers l'URL de connexion de l'API web (« /logout »).
Vous ajouterez un bouton de gestion de l'utilisateur dans le composant Header
de votre application, avec le CSS que vous estimerez nécessaire.
Vous constaterez que le composant accède aux données de l'utilisateur sans qu'aucune propriété de composant ne lui soit transmise. Le prix à payer pour cette simplicité d'utilisation est un couplage fort entre le composant et le contexte. En effet ce composant ne peut fonctionner que comme fils du fournisseur (« provider ») de contexte de l'utilisateur courant.
Firefox semble être parfois excessivement prudent pour la gestion des cookies inter-site. Pour que l'authentification fonctionne convenablement, vous devrez peut-être mettre une exception de sécurité pour l'API, afin qu'il accepte de transmettre le cookie de la session où vous étiez authentifié. Dans les paramètres de Firefox, dans la section « Vie privée et sécurité », vous pourrez ajouter une exception pour l'API en utilisant l'URL https://iut-rcc-infoapi.univ-reims.fr
:
UserButton
.
UserButton
dans l'en-tête de l'application.
Vous venez de voir comment obtenir des données depuis le contexte, mais ces données sont initialisées et modifiées dans le fournisseur de contexte. Vous allez maintenant voir comment permettre à l'utilisateur de modifier les données depuis un composant consommant le contexte.
De manière similaire à la gestion de l'utilisateur connecté, vous commencerez par créer un nouveau contexte React permettant de gérer la couleur du titre de l'application.
Le fournisseur du contexte exposera un object JavaScript contenant une variable d'état stockant la couleur du titre de l'application ainsi qu'une fonction permettant de générer une nouvelle couleur aléatoire dans la variable d'état.
Pour optimiser les ré-affichages de composant, vous pourrez définir la fonction permettant de générer une nouvelle couleur aléatoire comme une fonction persistante (useCallback
), avec comme dépendence la fonction d'affectation de la variable d'état.
Vous ajouterez ce fournisseur de contexte au composant App
.
Vous utiliserez la valeur exposée par le contexte pour définir la couleur du texte du titre de l'application en utilisant la propriété HTML style.
Vous devriez voir la couleur du titre de l'application changer lors de chaque rechargement de la page.
Header
.
Vous allez ensuite ajouter un nouveau composant Footer
représentant le pied de page de l'application. Sa fonction sera d'afficher l'utilisateur courant lorsqu'il est connecté.
Vous ajouterez aussi au pied de page de l'application un bouton qui sera le support du clic de la souris et permettra de générer une nouvelle couleur aléatoire pour le titre de l'application.
Une fois le composant Footer
ajouté à votre application, vous devriez voir la couleur du titre de l'application changer lors de chaque clic de la souris sur le bouton de changement de palette.
Footer
.
Footer
dans le composant App
.
Pour finir avec l'utilisation des contextes, vous allez ajouter une liste de notes au détail d'un utilisateur, lorsque l'utilisateur est connecté.
Vous allez encore devoir extraire l'identifiant d'un utilisateur de l'IRI retourné par l'API comme dans BookmarkDetail
et BookmarkItem
. Vous ajouterez un nouveau service de transformation iriToId.js dans le sous-répertoire « src/services/transformers » exportant une fonction réalisant la transformation d'un IRI en un nombre.
Ensuite, comme pour la liste des signets d'un utilisateur, vous ajouterez un nouveau service permettant d'obtenir la liste des notes d'un utilisateur, que vous encapsulerez dans un nouveau hook personnalisé useUserPaginatedRating
.
Vous pourrez ensuite créer les composants RatingItem
et RatingList
permettant d'afficher les notes d'un utilisateur. Et vous afficherez la liste des notes d'un utilisateur dans le détail d'un utilisateur.
La liste des notes d'un utilisateur n'est visible que si l'utilisateur est connecté. Dans le hook personnalisé useUserPaginatedRating
vous utiliserez les données du contexte de l'utilisateur pour identifier si l'utilisateur est authentifié et si non, vous retournerez directement un tableau vide sans réaliser de requête vers l'API.
iriToId
.
getUserRatings
.
useUserPaginatedRating
.
RatingItem
et RatingList
.
L'imbrication de nombreux fournisseurs de contexte dans le composant App
rend le composant peu lisible. Vous allez créer un composant Providers
qui recevra sous forme de propriété React (props) un tableau de fournisseur de contexte et qui imbriquera ces fournisseurs de contexte autour de ces enfants.
L'imbrication sera réalisée en ajoutant progressivement les « providers » autour de la propriété React children
à l'aide d'une boucle ou de la méthode reduce
.
Providers
.
Providers
dans le composant App
.