Naviguez avec des routes dynamiques

Découvrez les routes dynamiques

Au chapitre précédent, vous avez transformé l'application Shiny en Single Page Application avec quatre pages accessibles : Dashboard, Projets, Employés et À propos. Félicitations !

Mais si vous regardez bien la page Employés, vous verrez qu'elle affiche une liste de cartes avec les informations de base de chaque employé. Et si vous vouliez voir plus de détails sur Sophie Martin ou Marc Dubois ? Il faudrait une page de détails pour chaque employé !

Une première idée (mauvaise) serait de créer une route par employé :

<Route path="/employees/1" element={<Employee name="PierreMartin" />} />
<Route path="/employees/2" element={<Employee name="ThomasDubois" />} />
<Route path="/employees/3" element={<Employee name="MarieLefebvre" />} />
// ... et ainsi de suite pour les 10 employés

Mais cette approche pose plusieurs problèmes :

  • Vous devez créer 10 composants différents (un par employé)

  • Si vous ajoutez un employé, vous devez créer une nouvelle route

  • Le code est dupliqué : chaque composant affiche les mêmes informations, juste avec des données différentes

  • Imaginez une application avec 1000 utilisateurs... 

Heureusement, React Router a pensé à tout ! Les routes dynamiques permettent de créer une seule route qui s'adapte selon un paramètre variable dans l'URL.

Le principe est simple : au lieu d'écrire /employees/1/employees/2, etc., on écrit une seule route /employees/:id  où  :id  est un paramètre qui peut prendre n'importe quelle valeur.

Bonne nouvelle : la page EmployeeDetail  existe déjà dans votre application ! Elle se trouve dans  src/pages/EmployeeDetail/index.jsx. Actuellement, elle affiche toujours l'employé avec l'id 1. Notre objectif est de la faire afficher l'employé correspondant à l'URL tapée dans le navigateur.

Alors, partons à la découverte des routes dynamiques ! 

Créez une route avec paramètre

La syntaxe des paramètres de route

Dans React Router, on indique un paramètre dynamique avec le symbole deux-points suivi du nom du paramètre.

Voici quelques exemples :

/employees/:id   # → /employees/1, /employees/5, /employees/42
/projects/:projectId    # → /projects/3, /projects/12
/blog/:slug             # → /blog/react-router, /blog/hooks

Le nom après les  :  peut être ce que vous voulez :  :id:employeeId:userId:slug, etc. Vous récupérerez ensuite la valeur avec ce même nom grâce au hook useParams()  (nous verrons ça juste après !).

Ajoutez la route dans le router

Ouvrez votre fichier main.jsx. Actuellement, vous avez ces routes :

<Routes>
    <Route path="/" element={<Home />} />
    <Route path="/projects" element={<Projects />} />
    <Route path="/employees" element={<Employees />} />
    <Route path="/about" element={<About />} />
    <Route path="*" element={<Error />} />
</Routes>

Nous allons ajouter une nouvelle route pour les détails d'un employé. D'abord, importez le composant EmployeeDetail :

import EmployeeDetail from './pages/EmployeeDetail'

Puis ajoutez la route juste après la route /employees:

<Routes>
    <Route path="/" element={<Home />} />
    <Route path="/projects" element={<Projects />} />
    <Route path="/employees" element={<Employees />} />
    <Route path="/employees/:id" element={<EmployeeDetail />} />
    <Route path="/about" element={<About />} />
    <Route path="*" element={<Error />} />
</Routes>

Testez dans le navigateur

Lancez votre application avec npm run dev et testez différentes URLs dans votre navigateur :

  • http://localhost:5174/employees/1

  • http://localhost:5174/employees/5

  • http://localhost:5174/employees/42

Vous devriez voir la page EmployeeDetail  s'afficher ! Mais il y a un problème : quelle que soit l'URL, vous voyez toujours les détails du même employé (Sophie Martin avec l'id 1). 🤔

Pourquoi ?

Regardons le code de EmployeeDetail  pour comprendre.

Récupérez les paramètres avec useParams

Le problème du code actuel

Ouvrez le fichier src/pages/EmployeeDetail/index.jsx. Vous verrez ce code :

import { getEmployeeById } from '../../data/employees'
import './EmployeeDetail.css'


function EmployeeDetail() {

  const id = 1

  const employee = getEmployeeById(parseInt(id))

  if (!employee) {
    return (
      <div className="page">
        <div className="employee-not-found">
          <h1>Employé non trouvé 🙈</h1>
          <p>L'employé demandé n'existe pas.</p>
        </div>
      </div>
    )
  }

  return (
    <div className="page">
        <div className="employee-detail">
            {/* Details de l'employé*/}
        </div>
    </div>
  )
}

export default EmployeeDetail

Le problème est ici : const id = 1. L'ID est hardcodé (écrit en dur) à 1, donc peu importe l'URL que vous tapez, le composant affiche toujours l'employé avec l'id 1.

Comment récupérer l'ID depuis l'URL ? 

C'est là que le hook useParams()  entre en jeu !

Le hook useParams

React Router nous fournit le hook useParams()  qui permet de récupérer tous les paramètres définis dans l'URL.

Importez-le depuis react-router-dom  :

import { useParams } from 'react-router-dom'

Puis utilisez-le dans votre composant :

const { id } = useParams()

useParams()  retourne un objet JavaScript. Pour la route  /employees/:id, cet objet contient une clé id, par exemple  { id: "42" }. L’instruction  const { id } = useParams()  permet d’extraire directement la valeur de  id  depuis cet objet.

Modifiez EmployeeDetail

Voici le code complet modifié de EmployeeDetail :

import { useParams } from 'react-router-dom'
import { getEmployeeById } from '../../data/employees'
import './EmployeeDetail.css'

function EmployeeDetail() {
  // Récupération de l'ID depuis l'URL
  const { id } = useParams()

  const employee = getEmployeeById(parseInt(id))

  if (!employee) {
    return (
      <div className="page">
        <div className="employee-not-found">
          <h1>Employé non trouvé 🙈</h1>
          <p>L'employé demandé n'existe pas.</p>
        </div>
      </div>
    )
  }

  return (
    <div className="page">
      <div className="employee-detail">
        <div className="employee-detail-header">
          <img
            src={employee.avatar}
            alt={employee.name}
            className="employee-detail-avatar"
          />
          <div className="employee-detail-info">
            <h1>{employee.name}</h1>
            <p className="employee-detail-position">{employee.position}</p>
            <p className="employee-detail-department">{employee.department}</p>
          </div>
        </div>

        <div className="employee-detail-content">
          <section className="employee-detail-section">
            <h2>Biographie</h2>
            <p>{employee.bio}</p>
          </section>

          <section className="employee-detail-section">
            <h2>Contact</h2>
            <div className="employee-contact">
              <div className="contact-item">
                <strong>Email :</strong> {employee.email}
              </div>
              <div className="contact-item">
                <strong>Téléphone :</strong> {employee.phone}
              </div>
            </div>
          </section>

          <section className="employee-detail-section">
            <h2>Compétences</h2>
            <div className="employee-skills">
              {employee.skills.map((skill) => (
                <span key={skill} className="skill-badge">
                  {skill}
                </span>
              ))}
            </div>
          </section>
        </div>
      </div>
    </div>
  )
}

export default EmployeeDetail

Les changements sont simples :

  1. Import de useParams  depuis  react-router-dom

  2. Récupération de l'ID avec const { id } = useParams()

  3. Conversion de la string en nombre avec parseInt(id)

Profil détaillé de Sophie Martin, développeuse Full-Stack, avec biographie, contact et liste de compétences techniques affichés sur fond sombre.
Affichage dynamique du profil d’un employé en fonction de l’ID récupéré depuis l’URL.

Testez à nouveau

Retournez dans votre navigateur et testez différentes URLs :

  • /employees/1→ Pierre Martin (Développeur Full-Stack)

  • /employees/5  → Thomas Dubois (Designer UX/UI)

  • /employees/10  → Laura Chen (Data Scientist)

Ça fonctionne ! Chaque URL affiche l'employé correspondant.

Maintenant, testez avec un ID qui n'existe pas : /employees/999.

Vous devriez voir le message "Employé non trouvé 🙈". C'est exactement le comportement attendu ! Le  if (!employee) gère ce cas et affiche un message d'erreur plutôt qu'une page cassée.

Félicitations, vous maîtrisez les routes dynamiques ! 

Rendez les cartes employés cliquables

Notre route dynamique fonctionne parfaitement, mais il y a encore un problème pratique : pour voir les détails d'un employé, vous devez taper l'URL manuellement dans la barre d'adresse. Ce n'est pas très pratique ! 

 

L'idéal serait de pouvoir cliquer directement sur une carte d'employé dans la page /employees  pour accéder à ses détails. C'est ce que nous allons faire maintenant.

Modifiez EmployeeCard

Ouvrez le fichier src/components/EmployeeCard/index.jsx. Actuellement, c'est juste une div  qui affiche les informations :

function EmployeeCard({ employee }) {
  return (
      <div className="employee-card">
        <img
          src={employee.avatar}
          alt={employee.name}
          className="employee-avatar"
        />
        <div className="employee-info">
          <h3>{employee.name}</h3>
          <p className="employee-position">{employee.position}</p>
          <p className="employee-department">{employee.department}</p>
        </div>
      </div>
  )
}

Nous allons wrapper (envelopper) toute la carte dans un composantLinkde React Router. D'abord, importez Link :

import { Link } from 'react-router-dom'
import './EmployeeCard.css'

Puis modifiez le composant :

import { Link } from 'react-router-dom'
import './EmployeeCard.css'

function EmployeeCard({ employee }) {
  return (
    <Link to={`/employees/${employee.id}`} className="employee-card-link">
      <div className="employee-card">
     {/* Le contenu de la carte*/}
      </div>
    </Link>
  )
}

export default EmployeeCard

Point clé : Regardez le to  du  Link :to={/employees/${employee.id}}. C'est un template literal (avec les backticks `) qui permet de construire l'URL dynamiquement :

  • Pour Sophie Martin (id=1) :  /employees/1

  • Pour Marc Dubois (id=5) :  /employees/5

  • Pour Laura Chen (id=10) :  /employees/10

Ajustez le CSS

Pour que l'expérience utilisateur soit parfaite, ajoutez ces styles dans src/components/EmployeeCard/EmployeeCard.css :

.employee-card-link {
    text-decoration: none;
    color: inherit;
    display: block;
}

.employee-card {
/* ... vos styles existants ... */
    cursor: pointer;
}

Ces styles permettent :

  • text-decoration: none  : enlève le soulignement du lien

  • color: inherit  : garde les couleurs définies pour la carte

  • cursor: pointer  : change le curseur en main au survol (indication visuelle que c'est cliquable)

Testez la navigation

Retournez sur la page /employees  et survolez une carte. Le curseur devient une main 👆, indiquant que la carte est cliquable.

Cliquez sur une carte : vous êtes redirigé vers la page de détails de cet employé ! L'URL change pour /employees/X  et la page se met à jour, sans rechargement. C'est exactement le comportement d'une Single Page Application.

Vous venez de créer une navigation complète de la liste des employés vers leur page de détails, avec des routes dynamiques ! Félicitations !

Bonus : Navigation programmatique avec useNavigate

Jusqu'à présent, nous avons utilisé le composant Link  pour la navigation : l'utilisateur clique sur un lien et la navigation se fait. Mais parfois, vous avez besoin de naviguer par code, sans clic direct de l'utilisateur. Par exemple :

  • Rediriger après la soumission d'un formulaire

  • Rediriger après une authentification réussie

  • Ajouter un bouton "Retour" qui ramène à la page précédente

Pour ces cas, React Router fournit le hook useNavigate().

Le hook useNavigate

Importez-le depuis react-router-dom :

import { useNavigate } from 'react-router-dom'

Puis utilisez-le dans votre composant :

const navigate = useNavigate()

navigate  est une fonction que vous pouvez appeler pour naviguer vers une autre page :

// Navigation vers une route
navigate('/employees')
// Navigation avec un paramètre dynamique
navigate(`/employees/${id}`)
// Retour arrière (comme le bouton retour du navigateur)
navigate(-1)
// Avancer
navigate(1)

Exemple pratique : Bouton retour

Ajoutons un bouton "Retour à la liste" dans la page EmployeeDetail. Modifiez le composant :

import { useParams, useNavigate } from 'react-router-dom'
import { getEmployeeById } from '../../data/employees'
import './EmployeeDetail.css'

function EmployeeDetail() {
  
  // ....
  
  return (
    <div className="page">
      <button onClick={() => navigate('/employees')} className="back-button">
        ← Retour à la liste
      </button>

      <div className="employee-detail">
        {/* Details de l'employé */}
      </div>
    </div>
  )
}

export default EmployeeDetail

Au clic sur le bouton, navigate('/employees')  ramène l'utilisateur à la page de la liste des employés.

Styles du bouton retour

Ajoutez ces styles dans src/pages/EmployeeDetail/EmployeeDetail.css :

.back-button {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1.5rem;
    margin-bottom: 1.5rem;
    background: #646cff;
    color: #fff;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s ease;
}

.back-button:hover {
    background: #535bf2;
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(100, 108, 255, 0.3);
}

À vous de jouer !

Vous avez la base de la navigation dynamique pour les employés. C'est maintenant le moment de voler de vos propres ailes et d'appliquer ces concepts aux projets ! Retrouvez le code de depart sur la branche P1C3-Begin.

Exercice 1 - Créez la page de détails pour les projets

Contexte : La page ProjectDetail  n'existe pas encore. Vous allez la créer de A à Z en vous inspirant de  EmployeeDetail.

Vos objectifs :

  1. Créer le fichier de données

    • Ouvrez src/data/projects.js

    • Ajoutez la fonction getProjectById(id)  qui retourne un projet selon son ID

    • Inspirez-vous de getEmployeeById()  dans  employees.js

  2. Créer le composant ProjectDetail

    • Créez le dossier src/pages/ProjectDetail/

    • Créez index.jsx  et  ProjectDetail.css

    • Utilisez useParams()  pour récupérer l'ID du projet

    • Utilisez getProjectById()  pour récupérer les données

    • Affichez : nom, description, statut, progression, équipe, dates de début et fin

    • Gérez le cas où le projet n'existe pas

  3. Ajouter la route dans le router

    • Ouvrez src/main.jsx

    • Importez ProjectDetail

    • Ajoutez <Route path="/projects/:id" element={<ProjectDetail />} />

    • Placez cette route après /projects

  4. Rendre les ProjectCard cliquables

    • Ouvrez  src/components/ProjectCard/index.jsx

    • Importez  Link

    • Wrappez la carte avec  <Link to={/projects/${project.id}}>

    • Ajustez le CSS pour le curseur pointer

  5. Tester votre travail

    • Allez sur /projects

    • Cliquez sur un projet → vous devriez voir les détails

    • Testez /projects/999  → message d'erreur attendu

Exercice 2 - Ajoutez des boutons retour

Vos objectifs :

  1. Dans EmployeeDetail et ProjectDetail

    • Importez useNavigate

    • Ajoutez un bouton "← Retour à la liste"

    • Au clic : navigate('/employees')  ou navigate('/projects')

    • Stylisez le bouton dans le CSS

  2. Bonus : Testez navigate(-1)

    • Remplacez navigate('/employees')  par  navigate(-1)

    • Testez la différence de comportement

    • Quelle approche préférez-vous et pourquoi ? 

La solution se trouve sur la branche P1C3-Solution.

En résumé

  • Les routes dynamiques utilisent la syntaxe :nomParametre  pour créer des URLs variables (ex :  /employees/:id).

  • Le hook  useParams()  permet de récupérer les valeurs des paramètres depuis l'URL sous forme d'objet.

  • Les paramètres retournés par useParams()  sont toujours des strings, pensez à les convertir avec parseInt()  si besoin.

  • On peut combiner  Link  et routes dynamiques pour créer une navigation interactive, par exemple de la liste vers les détails.

  • Le hook  useNavigate() permet la navigation programmatique, utile pour les redirections après validation ou soumission.

  • L'ordre des routes est important : placez toujours les routes spécifiques avant les routes génériques pour éviter les conflits.

Vous maîtrisez maintenant les routes dynamiques dans React Router ! Votre application Shiny permet de naviguer de la liste des employés vers leurs détails de manière fluide et sans rechargement de page.

Au prochain chapitre, nous allons découvrir les routes imbriquées et les Outlets pour structurer encore mieux notre application. Alors, rendez-vous au prochain chapitre !

Ever considered an OpenClassrooms diploma?
  • Up to 100% of your training program funded
  • Flexible start date
  • Career-focused projects
  • Individual mentoring
Find the training program and funding option that suits you best