Skip to main content

TP2 - React Router - Partie 2

Filtrage par catégorie#

Vous l'avez certainement remarqué : nous avons déplacé dans le composant ReviewList une liste déroulante (select) cachée. Retirez-lui la classe hidden afin de voir de quoi il retourne.

Comme vous pouvez le voir, il s'agit d'un filtre permettant de n'afficher qu'une certaine catégorie de vins. En l'état actuel, le choix est très limité.

Faire fonctionner la select#

Modifiez cette liste déroulante de manière à afficher la liste de toutes les catégories du ficher JSON, que vous pouvez maintenant importer de la sorte :

import{ reviews, categories }from​ ​'../api/db.json'

Afin de faire fonctionner cet élément (ainsi que le filtrage), notre composant devra se souvenir de l'option actuellement sélectionnée.

Faites les modifications appropriées pour implémenter ces fonctionnalités.

Une fois ceci terminé, faites un commit avec le commentaire "Ex 6 : Category filters" et poussez vers le serveur.

git add .git commit -m 'Ex 6 : Category filters'git push

Navigation programmatique#

Imaginons maintenant qu'un internaute visitant votre application souhaite ajouter la page des "Bordeaux" en favoris. Comme vous vous en doutez, cela ne fonctionnera pas en l'état actuel : pour qu'un utilisateur puisse ajouter une catégorie comme favoris de son navigateur (ou la partager avec un ami sur Discord...), il nous faut modifier l'URL courante quand on sélectionne un élément de la liste déroulante.

Malheureusement, notre select et ses options ne sont pas des éléments Link, ils ne permettent donc pas directement de modifier l'adresse courante de manière déclarative. Pour répondre à ce type de contraintes, React Router expose un hook supplémentaire : useNavigate.

Voici un exemple d'utilisation :

const CheckAge = () => {    const [birth, setBirth] = useState('')    const navigate = useNavigate()
    const handleSubmit = e => {        e.preventDefault()        if(new Date().getFullYear() - new Date(birth).getFullYear() >= 18){            navigate('/adult-stuff')        } else {            navigate('/home')        }    }        return <form onSubmit={handleSubmit}>        <label htmlFor="birthdate">Date de naissance</label>        <input            id="birthdate"            type="date"            onChange={e => setBirth(e.target.value)}            value={birth}        />        <input type="submit" value="Valider" />    </form>}

Dans cet exemple, on souhaite vérifier l'age de l'utilisateur en lui demandant de rentrer sa date de naissance. On souhaite rediriger l'utilisateur vers du contenu pour adulte si il a plus de 18 ans, ou vers la page d'accueil si il est mineur. On se sert donc de la fonction navigate pour initier une navigation programmatique vers la page adaptée.

Utilisez cet exemple et vos connaissances sur les routes pour faire naviguer l'utilisateur quand il sélectionne un élément de la liste déroulante. Retirez ensuite le state de votre composant pour faire en sorte que la valeur courante de la liste déroulante soit contrôlée par le routeur à l'aide du hook useParams.

Une fois ceci terminé, faites un commit avec le commentaire "Ex 7 : Params as value" et poussez vers le serveur.

git add .git commit -m 'Ex 7 : Params as value'git push

Login, redirections et pages protégées#

Pour cet exercice en deux parties, vous allez ajouter à votre application une page contenant un formulaire permettant d'ajouter une critique de vins. Cependant, cette page ne devra être accessible qu'à un utilisateur connecté.

Ajout de critiques#

Dans un premier temps, créez dans un fichier éponyme un composant ​AddReview, ainsi qu'une route pour y accéder : ​/add.

Ce composant devra comporter un formulaire contenant les champs :

  • Author
  • Title
  • Wine
  • Category
  • Price
  • Review

Essayez de composer cette page vous même : vous pouvez utiliser la bibliothèque BareCSS qui est déjà incluse dans ce projet.

Chacun de ces élément doit bien entendu être contrôlé, et la sélection de la catégorie devra se faire à l'aide d'une liste déroulante.

Cela n'a pas de réel intérêt pour la suite des exercices, mais reste une révision bienvenue.

Pages Login et Logout#

Créez un composant Login dans un nouveau fichier Auth.jsx. Ajoutez-y un formulaire très simple comportant juste un champs "Login" et un champs "Password". Lors de la soumission du formulaire, transmettez ces informations en appelant une fonction passée dans une prop onLogin. ​

Créez ensuite un composant Logout dans ce même fichier, qui sera une simple page de confirmation permettant à l'utilisateur de se déconnecter à l'aide d'un bouton.

N'oubliez pas d'exporter ces composants de votre fichier ! Créez ensuite deux routes, ​/login​ et ​/logout, qui conduiront l'utilisateur aux composants appropriés. Nous n'allons évidemment pas implémenter une vraie authentification, mais le concept n'est pas très différent. Notre application doit juste garder en mémoire le fait que l'utilisateur soit connecté ou non, ainsi que son nom d'utilisateur.

Le composant qui sera le garant de cet état sera le composant App, qui est le plus haut dans le hiérarchie, et qui pourra ainsi passer ces informations en ​props​ aux composants enfants qui en auront besoin.

Ajoutez donc un ​state​ comprenant ces informations au composant App. Créez les méthode handleLogin​ et ​handleLogout​ modifiant ce ​state​ et passez les respectivement aux composant Login et Logout par leurs ​props.

Une fois ceci terminé, faites un commit avec le commentaire "Ex 8 : Authenticating users" et poussez vers le serveur.

git add .git commit -m 'Ex 8 : Authenticating users'git push

Redirections déclaratives#

Nous avons maintenant toutes les pages que nous souhaitions ajouter, mais toutes ces pages sont accessibles par défaut : vous pouvez vous rendre sur /logout sans être authentifié par exemple, ou aller sur la page /login alors que vous êtes déjà authentifié, ou encore aller sur la page /add sans être authentifié.

Il manque dans notre application des redirections. React Routeur propose plusieurs solutions pour ce faire : faire des appels à navigate comme nous l'avons fait précedemment (méthode impérative), ou utiliser le composant Navigate (méthode déclarative).

Pour notre cas d'usage, il est préférable d'utiliser le composant Navigate : ce composant, quand il est affiché, redirige l'utilisateur vers l'adresse qui lui est donnée. On peut donc s'en servir avec de l'affichage conditionnel à l'aide d'une prop isAuthenticated par exemple...

Essayez d'ajouter ce composant dans votre page ​AddReview, de manière à ce que l'utilisateur soit redirigé vers la page Login si il n'est pas authentifié.

tip

Allez observer la documentation de React Router pour ce composant Navigate. Ce composant peut prendre une prop (booléenne) replace. Essayez de lui passer, et observez la différence en jouant avec les boutons précédent et suivant de votre navigateur.

Une fois que vous aurez réussi, ajoutez les redirections suivantes :

  • Rediriger l'utilisateur sur la page d'accueil depuis la page Login si il est authentifié
  • Rediriger l'utilisateur sur la page Login depuis les pages ​AddReview​ et ​Logout​ si il n'est pas authentifié

Route state#

Maintenant, quand un utilisateur souhaite ajouter une critique, il est redirigé vers la page de connection, puis une fois connecté vers la page d'accueil.

Il serait plus logique de le rediriger vers la page qu'il a demandé à l'origine, soit par exemple ​AddReview​.

Le composant Navigate accepte une chaîne de caractères comme prop to, mais peut aussi prendre un objet.

note

Pour information, l'objet que l'on peut passer peut prendre les propriétés suivantes :

  • pathname : le chemin de l'URL
  • search : la querystring de l'URL
  • hash : le hash de l'URL (ancre HTML)

Par ailleurs, ce composant accepte aussi une prop supplémentaire : state. Cette prop permet de stocker un état supplémentaire pour cette navigation.

La documentation actuelle n'est pas très claire à ce sujet.

On peut utiliser la propriété state de cet objet pour passer des informations d'une page à l'autre sans les mettre dans l'URL. Dans notre cas, on peut par exemple stocker la page dont on vient, afin de pouvoir y rediriger l'utilisateur ensuite :

{/* Sur une page protégée */}{!user && <Navigate    to={{        pathname:​ ​'/login'    }}    ​state={{         from:​ ​'/protected'    }}    replace/>}

On peut ensuite dans une route accéder à la valeur de cet objet state à l'aide du hook useLocation :

const LocationDisplay = () => {    const location = useLocation()        return <ul>        <li>            I'm on {location.pathname}        </li>        {location.state && <li>            I'm coming from {location.state.from}        </li>}    </ul>}

A l'aide de ces informations faites en sorte de rediriger l'utilisateur sur la page à laquelle il essayait d'accéder une fois qu'il s'est authentifié.

Pour finaliser cet exercice, dans le composant Layout, ajoutez à droite de la barre de navigation un lien qui affichera "Login" si l'utilisateur est authentifié et "Logout" si il ne l'est pas, pointant sur la page correspondante.

Une fois ceci terminé, faites un commit avec le commentaire "Ex 9 : Redirecting & route state" et poussez vers le serveur.

git add .git commit -m 'Ex 9 : Redirecting & route state'git push

Les routes imbriquées#

Histoire d'aller un peu plus loin, nous allons ajouter à notre application des routes imbriquées.

Allez voir sur la page du résultat de ce TP, et essayez de sélectionner une catégorie, puis de cliquer sur une critique. Comme vous pouvez le voir, la critique s'affiche au dessus de la select des catégories. C'est un peu difficle à observer dans la correction, mais l'adresse dans la barre d'adresse est par exemple /pomerol/view/barrel-sample-173.

React Router donne la possibilité d'ajouter des routes imbriquées : des routes dans des routes. Il suffit pour ce faire de déclarer une route à l'intérieur d'une autre route, comme par exemple :

<Routes>    <Route path="users:userid" element={<User />}>        <Route path="edit" element={<UserEdit />}>    </Route><Routes>

Ici, l'utilisateur navigant sur /users/5 verra le composant User. Si il navigue ensuite vers /users/5/edit, il verra le composant UserEdit à l'intérieur du composant User.

Le composant User, pour pouvoir utiliser cette fonctionnalité doit définir dans son code un endroit ou placer les élements imbriqués qu'il peut recevoir. Pour ce faire, React Router expose un composant Outlet, qu'on peut utiliser de la sorte :

import {Outlet} from 'react-router-dom'
const User = () => {    return <div>        <h1>Page utilisateur</h1>        {/* ... */}        <Outlet />    </div>}

À l'aide de ces informations, modifiez vos routes ainsi que votre composant ReviewList afin de pouvoir être capable d'afficher le composant Review depuis l'adresse /:category/view/:slug.

Une fois ceci terminé, faites un commit avec le commentaire "Ex 10 : Nested routes" et poussez vers le serveur.

git add .git commit -m 'Ex 10 : Nested routes'git push

Conclusion et perspectives d'amélioration#

Félicitations ! Vous savez maintenant utiliser React Router, du moins une bonne partie de ses fonctionnalités. N'hésitez pas à en explorer la documentation pour en apprendre davantage !

Pour améliorer encore votre application, vous pourriez par exemple :

  • Lui ajouter une barre de recherche liée au routeur (​query string​)
  • Proposer des options de tri de la liste (nom, prix...)
  • Ajouter un système de pagination pour limiter le nombre par page
  • Implémenter l'ajout de critique