Laboratoire 3.1: Transmission de données (Partie 1)
Introduction
Dans les laboratoires précédents, nous avons vu comment gérer un composant et son état (et donc son affichage). C'est un bon départ, mais on ne peut clairement pas faire un unique gros composant pour toute une application. Cela enfreindrait les principes des composants vus au cours. Ainsi, nous allons devoir en créer plusieurs et les faire communiquer ! Il existe plusieurs façons de faire pour transmettre de l'information. Nous allons explorer plusieurs de ces possibilités dans ce laboratoire.
Exercices
Nous allons réaliser une mini application qui comportera deux vues:
- la vue principale qui contiendra un tableau. Ce tableau listera les employés avec le nom, le prénom et un lien cliquable pour pouvoir éditer les informations facilement.
- la vue secondaire affichera les informations d'un employé et permettra de les modifier.
Nous allons développer cette application au fur et à mesure de ce laboratoire (qui est en deux parties). A chaque fois que nous verrons une technique de communication entre les composants, nous ferons la partie de l'application qui lui est associée.
Comme d'habitude, créez un nouveau projet avec la commande suivante:
npm create vite@latest laboratoire_3 -- --template react
Communication
Les 3 stratégies que nous allons voir pour communiquer des informations sont:
- via les props
- via les URL
- via les stores
Ces 3 techniques sont classées par ordre de difficulté. Elles sont indispensables, car chacune a ses particularités !
1. Via props
Les propriétés (en abrégé: props) sont des données passées à un composant lors de sa création. Exemple:
<MonComposant param1={value1} param2={value2}>
{/*Reste du code ici*/}
<MonComposant/>
Dans ce code, vous passez des informations au composant MonComposant:
param1avecvalue1param2avecvalue2
Ces paramètres seront regroupés dans un objet par React et qui sera passé en paramètre lors de l'appel à votre composant.
Du côté du composant, pour avoir accès à cet objet, il suffit de mettre un paramètre dans la définition du composant. Exemple:
function MonComposant(props){
{/*pour accéder à la propriété param1, il suffira de faire props.param1 dans le code*/}
}
props est employé depuis des années, il s'agit d'une convention entre développeurs. Donc, évitez de donner un autre nom à ce paramètre.
Sachez que cette écriture peut-être simplifée en utilisant la destructuration !
function MonComposant({param1, param2}){
{/*on pourra utiliser directement param1 au lieu de props.param1*/}
}
Et si une des propriétés change ? Il se passe quoi ?
Réponse

Mais pourquoi donc ? Souvenez-vous: un composant se met à jour (et donc son affichage) uniquement si son état change. Donc, si une propriété change et pas l'état, alors le composant ne sera pas re-rendu. Comment pouvons-nous régler ce problème ?

Souvenez-vous du laboratoire précédent. Quand un élément effectue un nouveau rendu, ses enfants le font également. Dans notre cas, si le composant parent de MonComposant effectue un nouveau rendu alors ses enfants aussi. MonComposant va donc recevoir les nouvelles propriétés et sera re-rendu à son tour !

Comment faire pour qu'un enfant puisse mettre à jour son parent ?
L'exercice est un plus compliqué... En effet, quand un enfant effectue un rendu, son parent n'est pas affecté (et heureusement !). Pour effectuer cette action, l'enfant devrait être capable de changer l'état de son parent. Pour changer l'état d'un composant, il faut utiliser la fonction de mise à jour associée à l'état. Il suffit donc que l'enfant utilise la fonction de mise à jour de son parent ! Comme il est possible de passer des fonctions comme paramètres, nous pouvons passer cette fonction via les props !
function MonSuperComposant(){
const [nom, setNom] = useState('');
return (
<>
<MonSousComposant callback={(nom) => setNom(nom)}/>
<p>Votre nom est {nom}</p>
</>
)
}
function MonSousComposant({callback}){
return (
<>
<label>Tapez votre nom: </label>
<input type="text" onChange={event => callback(event.target.value)}/>
</>
)
}
Exercice
Pour cette partie, nous allons créer un composant qui affichera un tableau en prenant en paramètres une liste d'employés à afficher. Voici un exemple d'appel à ce composant:
const employes = [
{
id: 1,
nom: "Anakin ",
prenom: "Skywalker"
},
{
id: 2,
nom: "R2",
prenom: "D2"
}
];
return (
<ListeEmployes employes={employes}/>
)
Attention, ce composant sera appelé dans App et c'est App qui contiendra la liste des employés. Dans App vous devrez aussi créer deux champs et un bouton pour pouvoir ajouter un nouvel employé. Cela signifie que ListeEmployes n'aura pas d'état ! Pensez aussi à gérer les id des employés correctement !
2. Via URL
Actuellement quand nous arrivons sur notre application, nous arrivons toujours sur le premier composant de notre application. Nous sommes donc obligés de faire plusieurs clics pour arriver à l'endroit désiré. Par exemple, si nous avons un blog avec des articles: nous serions obligés de retrouver l'article nous-mêmes, car dans tous les cas nous arriverions sur la page d'accueil de notre site.
Ce que nous désirons, c'est la possibilité d'utiliser un lien comme "monsupersite.be/article/3" et que l'application charge le composant Article. Pour arriver à faire cela, il faut lire l'URL (via windows.location.pathname), récupérer les informations (les valeurs et les paramètres dans l'URL) et charger le composant avec ces informations.
Ceci pourrait être rapidement compliqué. Heureusement, nous pouvons utiliser une librairire (React Router) qui nous permettra de nous faciliter la vie ! Ouvrez une console dans le dossier de votre projet et exécutez l'instruction suivante:
npm i react-router-dom
L'idée de la libraire est simple: votre composant App contiendra un Router (il existe plusieurs types de routeur). Il devra lire l'URL et charger le bon composant grâce à des composants Route (qui font la correspondance entre une URL et le composant à charger). C'est lui qui s'occupera d'enlever les composants de "l'ancienne page" et de monter ceux de la nouvelle page.
Dans cet exemple, nous allons essayer de construire une petite application qui affichera un article si on utilise une URL /article/ID (où ID sera un nombre). Dans tous les autres cas, nous afficherons la page d'accueil !
import { createBrowserRouter } from "react-router-dom";
import Accueil from "../components/Accueil";
import Article from "../components/Article";
const router = createBrowserRouter([
{
path: "/article/:id",
element: <Article />,
},
{
path: "/*",
element: <Accueil/>
}
]);
export default router;
Explications ! createBrowserRouter prend un tableau de Route. Pour chaque élément du tableau, on précise quel composant (element) correspond quelle URL (path). Pour le premier élément, nous avons un chemin avec une partie dynamique. Cela signifie que notre route sera bonne pour les urls suivantes:
- article/1
- article/2
- article/42
- ...
La partie dynamique pourra être récupérée dans notre composant Article pour connaître l'id de l'article via le hook useParams() de React Router.
Enfin, nous avons une route avec un "joker" ("wild cart" en anglais) pour indiquer que toutes les routes commençant par un / correspondent au pattern.
Mais alors l'URL /article/42 correspond aussi à /* ?
Exact ! Mais React Router prend toujours la route la plus précise pour trouver le bon élément, grâce à son algorithme.
Plus une route est précise, plus elle devrait être située haut dans votre fichier ! Cela facilitera grandement la lecture du code !
Passons à notre composant Article maintenant !
import {Link, useParams} from 'react-router-dom';
//Nous n'avons pas encore vu comment récupérer des informations via une API,
//nous simulerons donc une liste d'articles en attendant.
const articles = [
{
id: 1,
titre: "Titre 1",
contenu: "Super texte numéro 1"
},
{
id: 2,
titre: "Titre 2",
contenu: "Super texte numéro 2"
},
{
id: 42,
titre: "Titre 42",
contenu: "Super texte numéro 42"
}
];
function Article(){
const params = useParams() // on récupère les paramètres de l'URL
const article = articles.find(a => a.id === parseInt(params.id)) // récupère l'article avec le même id que params.id
return (
<>
<h1>{article.titre}</h1>
<p>{article.contenu}</p>
<Link to="/">Retour à l'accueil</Link>
</>
)
}
export default Article;
Link est un composant permettant de laisser gérer la navigation par React Router. C'est l'équivalent de la balise a en HTML (la propriété to correspond au href).
Il existe une meilleure approche pour récupérer les paramètres. En effet, le code ci-dessus crée un couplage avec le router. Si nous désirons utiliser notre composant Article sans l'URL, cela serait impossible. Une meilleure approche serait de fournir l'id via les props !
Maintenant, faisons notre deuxième composant qui nous servira de page d'accueil.
import {Link} from 'react-router-dom';
const ids = [1, 2, 42];
function Accueil(){
return(
<>
<h1>Bienvenue sur le site !</h1>
<p>Voici nos différents articles:</p>
<ul>
{ids.map(i => {
return(
<li key={i}><Link to={"/article/" + i}>Article n°{i}</Link></li>
)
})}
</ul>
</>
);
}
export default Accueil;
Parfait ! Notre composant Accueil va générer une liste avec les bons liens. Quand on cliquera sur un lien, le routeur prendra le relais et démontera ce composant pour laisser place à Article. Il ne nous reste plus qu'à changer le code d'App pour qu'il appelle notre routeur.
import { RouterProvider } from "react-router-dom";
import router from "./routes/Router";
function App(){
return <RouterProvider router={router}/>
}
export default App;
Et voilà ! Nous avons mis en place un système permettant de "naviguer" (l'illusion de naviguer pour être plus précis) sur notre application ! Le router se charge de monter/démonter les composants et de leur passer les bonnes informations !

PS: si vous désirez un tutoriel plus complet sur la librairie, vous pouvez essayer celui de la documentation officielle
Exercice
Nous allons créer deux nouveaux composants: Accueil et Employe auxquels nous allons attacher les URL.
Nous avons 2 URL dans notre application:
/: qui affiche la vue principale => sera lié àAccueil./employe/:id: qui affichera la vue secondaire (affiche les informations de l'employé avec l'ID passé dans l'URL) => sera lié àEmploye.
Vous l'aurez compris: Accueil va appeler ListeEmployes (et donc, ce sera dorénavant lui qui contiendra la liste des employés et la passera à ListeEmployes). Employe sera notre composant pour éditer nos employés (nom et prénom uniquement).
Modifiez le composant ListeEmployes pour qu'il prenne un paramètre supplémentaire: editBasePath qui lui permettra de connaître l'URL pour éditer les informations d'un employé.
Vous serez rapidement confrontés à un problème: il est impossible de récupérer la liste des employés dans Employe pour afficher le prénom et le nom de la personne. Pour résoudre ce problème, il faudra "partager" cette liste entre composants et nous allons apprendre comment faire dans la seconde partie de ce laboratoire !
Si vous désirez de l'aide
- Router.jsx
- Accueil.jsx
- ListeEmployes.jsx
- Employe.jsx
import { createBrowserRouter } from 'react-router-dom';
import Employe from '../Employe.jsx';
import Accueil from '../Accueil.jsx';
const router = createBrowserRouter([
{
path:'/employe/:id',
element: /*TODO*/
},
{
path: '/*',
element: /*TODO*/
}
]);
export default router;
import { useState } from 'react';
import './App.css';
import ListeEmployes from './ListeEmployes.jsx';
function Accueil() {
//TODO Gérer l'état
return (
<>
<ListeEmployes /*TODO*/ />
<label>Prénom: </label>
<input type={'text'} onChange={/*TODO*/}/>
<br/>
<label>Nom: </label>
<input type={'text'} onChange={/*TODO*/}/>
<br/>
<button onClick={() => {
/*TODO*/
}}>Ajouter
</button>
</>
);
}
export default Accueil;
import {Link} from 'react-router-dom';
function ListeEmployes({employes, editBasePath}){
return(
<table>
<thead>
<tr>
<td>Prénom</td>
<td>Nom</td>
<td>Modifier</td>
</tr>
</thead>
<tbody>
{employes.map(e => {
return(
<tr key={/*TODO*/}>
<td>{/*TODO*/}</td>
<td>{/*TODO*/}</td>
<td>
<Link /*TODO*/ >Modifier</Link>
</td>
</tr>
);
})}
</tbody>
</table>
);
}
export default ListeEmployes;
import {useState} from 'react';
import {Link, useNavigate, useParams} from 'react-router-dom';
function Employe(){
//TODO: gérer l'état
return(
<>
<label>Prénom: </label>
<br/>
<input type={'text'} value={/*TODO*/} onChange={/*TODO*/}/>
<br/>
<label>Nom: </label>
<br/>
<input type={'text'} value={/*TODO*/} onChange={/*TODO*/}/>
<br/>
<button onClick={() => {
/*TODO + revenir à l'accueil*/
}}>Enregistrer modification</button>
<br/>
<Link to={/*TODO*/}>Retour à l'accueil</Link>
</>
);
}
export default Employe;