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/bookmarksApi.js » qui retournera une promesse des données de l'utilisateur si celui-ci est connecté et une promesse de undefined 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 au travers de la variable globalThis.
Vous penserez à protéger la valeur du paramètre à l'aide de la fonction encodeURIComponent.
Le navigateur Firefox a introduit un système de gestion des cookies cloisonnant les requêtes intersites asynchrones avec leurs propres cookies. Vous devez ajouter une exception pour autoriser les cookies pour le serveur de l'API :
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 « userContext.js » dont le rôle principal sera d'exporter par défaut le contexte de l'utilisateur courant. Le context répondra à une annotation JSDoc de ce type :
Le type Me correspond à la définition de type de la ressource retournée par la requête \me.
Vous ajouterez ensuite, dans un sous-répertoire « src/contexts/providers », un nouveau script « UserContextProvider.jsx » exportant le fournisseur (« provider ») de contexte pour l'utilisateur courant. Pour ce faire, vous exporterez un composant React d'ordre supérieur UserContextProvider dont le rendu sera composé du fournisseur (« provider ») de contexte de l'utilisateur, encapsulant les fils du composant.
Le composant UserContextProvider comportera deux variables d'état : userData et resolved. La première stockera les données de l'utilisateur et la seconde indiquera si les données ont étées obtenues depuis l'API. Le context pourra donc être dans l'un de ces 3 états :
resolved vaut false,
resolved vaut true et la variable userData contient les données émises par l'API web.
resolved vaut true et la variable userData vaut undefined.
La valeur par défaut de resolved sera false et celle de userData sera undefined, jusqu'à ce que le composant obtienne une réponse de la fonction getMe, émise depuis un hook useEffect.
La valeur du contexte initialisée par le fournisseur (« provider ») de contexte contiendra les variables d'état userData et resolved.
Vous rendrez le contexte de l'utilisateur accessible à tous les composants de l'application en utilisant le composant UserContextProvider, 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 lors du chargement de l'application.
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 courant pour obtenir ses données, en utilisant le hook useContext.
Selon la valeur des données du contexte de l'utilisateur courant, le composant affichera :
resolved est false.
resolved est true et que la propriété userData est undefined.
resolved est true et que la propriété userData contient un utilisateur.
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 dé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.
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.
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.
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 fournisseurs de contexte et qui imbriquera ces fournisseurs de contexte autour de ces enfants, comme décrit dans la JSDoc suivante :
L'imbrication sera réalisée en ajoutant progressivement les « providers » autour de la propriété React children à l'aide d'une boucle.
Vous simplifierez ensuite le composant App en utilisant ce nouveau composant.
Providers.
Providers dans le composant App.
Pour finir avec l'utilisation des contextes, vous allez ajouter une liste de notes au détail d'un signet, lorsque l'utilisateur est connecté.
Vous commencerez par ajouter un nouveau services permettant une requête vers la ressource /bookmarks/{id}/ratings permettant d'obtenir la liste des notes d'un signet, ainsi que les types associés.
Vous créerez aussi un nouveau hook personnalisé usePaginatedRatings permettant de gérer le résultat de la requête.
Vous pourrez ensuite créer les composants RatingItem et RatingsList permettant d'afficher une collection de notes. Et vous afficherez la liste des notes d'un signet dans le détail d'un signet.
Cette liste n'est visible que si l'utilisateur est connecté. Vous veillerez à détecter ce cas et à ne rien afficher lorsque l'utilisateur n'est pas connecté.
usePaginatedRating.
RatingItem et RatingsList.