
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ésMais 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 !
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/hooksLe 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 !).
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>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.
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 !
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.
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 :
Import de useParams depuis react-router-dom
Récupération de l'ID avec const { id } = useParams()
Conversion de la string en nombre avec parseInt(id)

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 !
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.
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
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)
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 !
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().
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)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.
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 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.
Contexte : La page ProjectDetail n'existe pas encore. Vous allez la créer de A à Z en vous inspirant de EmployeeDetail.
Vos objectifs :
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
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
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
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
Tester votre travail
Allez sur /projects
Cliquez sur un projet → vous devriez voir les détails
Testez /projects/999 → message d'erreur attendu
Vos objectifs :
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
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.
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 !