Introduction
React est une librairie JavaScript open source pour construire des interfaces utilisateur modulaires et réactives à partir de composants réutilisables.
En construisant une application comme un assemblage de composants, on favorise la modularité (chaque composant a une responsabilité claire) et la réutilisabilité (un même composant sert à plusieurs endroits).
Un premier composant
Fichier HelloWorld.jsx
function HelloWorld() {
return <h1>Hello, World!</h1>;
}
export default HelloWorld;
Un composant est une fonction qui retourne un élément JSX.
Pour l’utiliser, on l’écrit comme une balise :
Exemple: Utilisation de HelloWorld.jsx dans App.jsx (code ci-dessous)
import HelloWorld from "./HelloWorld";
function App() {
return (
<div>
<HelloWorld />
</div>
);
}
Composer des composants
Une interface se décompose naturellement en sections. Dans React, chaque section devient un composant. Pour un panneau de signalement d’arrivée des autobus :
// PanneauSignalement.jsx
import EnTete from "./EnTete";
import SelecteurArret from "./SelecteurArret";
import ListeSignalements from "./ListeSignalements";
import PiedPage from "./PiedPage";
function PanneauSignalement() {
return (
<div>
<EnTete />
<SelecteurArret />
<ListeSignalements />
<PiedPage />
</div>
);
}
export default PanneauSignalement;
L’arbre des composants ressemble à ceci :
App
└── PanneauSignalement
├── EnTete
├── SelecteurArret
├── ListeSignalements
└── PiedPage
Le point d’entrée de l’application monte ce composant racine dans la page :
// main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import PanneauSignalement from "./PanneauSignalement";
createRoot(document.getElementById("root")).render(
<StrictMode>
<PanneauSignalement />
</StrictMode>
);
Retourner plusieurs éléments
Un composant retourne toujours un seul élément racine. Retourner plusieurs éléments frères provoque une erreur :
// Erreur : deux éléments racine
function EnTete() {
return (
<h1>Arrivées d'autobus</h1>
<p>Réseau STM</p>
);
}
La solution conseillée est le Fragment (<>...</>), qui groupe sans ajouter de balise au DOM :
function EnTete() {
return (
<>
<h1>Arrivées d'autobus</h1>
<p>Réseau STM</p>
</>
);
}
On peut aussi employer un élément englobant, par exemple un <div>, quand un conteneur réel est utile :
function EnTete() {
return (
<div>
<h1>Arrivées d'autobus</h1>
<p>Réseau STM</p>
</div>
);
}
Le Fragment s’utilise quand un <div> serait superflu.
Les props
Un composant devient réutilisable quand son contenu peut varier. Les props (propriétés) transmettent de l’information d’un composant parent à un composant enfant.
// Parent.jsx
import Enfant from "./Enfant";
function Parent() {
const message = "Prochain 11 dans 3 min";
return (
<div>
<h1>Signalement</h1>
<Enfant message={message} />
</div>
);
}
export default Parent;
// Enfant.jsx
function Enfant(props) {
return <p>{props.message}</p>;
}
export default Enfant;
Le parent passe message comme attribut JSX ; l’enfant le reçoit dans l’objet props et l’affiche.
Destructuration des props
La forme props.message fonctionne, mais on préfère souvent destructurer les props dans la signature de la fonction :
// Enfant.jsx
function Enfant({ message }) {
return <p>{message}</p>;
}
Cette écriture est plus concise et rend visibles d’un coup d’oeil les props attendues. On peut aussi donner des valeurs par défaut :
function Bouton({ libelle = "Cliquez", actif = true }) {
return <button disabled={!actif}>{libelle}</button>;
}
Afficher une liste de composants
Dans une application réelle, on affiche rarement un élément seul : on affiche une liste. On utilise .map() pour transformer un tableau de données en un tableau d’éléments JSX.
function CarteSignalement({ ligne, arret, delai }) {
return (
<div className="carte-signalement">
<h2>Ligne {ligne}</h2>
<p>{arret}, arrive dans ~{delai} min</p>
</div>
);
}
function ListeSignalements() {
const signalements = [
{ id: 1, ligne: "11", arret: "Mont-Royal / Saint-Denis", delai: 3 },
{ id: 2, ligne: "747", arret: "Berri-UQAM", delai: 2 },
{ id: 3, ligne: "80", arret: "Parc / Beaubien", delai: 6 }
];
return (
<div className="liste-signalements">
{signalements.map((s) => (
<CarteSignalement
key={s.id}
ligne={s.ligne}
arret={s.arret}
delai={s.delai}
/>
))}
</div>
);
}
export default ListeSignalements;
Props et état
Avant de construire le compteur, une distinction centrale.
| Props | État (state) | |
|---|---|---|
| Source | Fournies par le parent | Internes au composant |
| Mutabilité | Immuables | Modifiables via setXxx |
| Effet sur le rendu | Change quand le parent passe de nouvelles valeurs | Change quand setXxx est appelé |
Les props configurent un composant (le parent dit « voici tes paramètres »). L’état mémorise quelque chose qui évolue pendant la vie du composant (la valeur d’un champ, l’onglet actif, le contenu d’un panier).
Construire un compteur
On a déjà construit un compteur en JavaScript natif dans Les bases du DOM. Le voici en React, pour voir précisément ce que useState change.
On le construit par étapes, pour comprendre pourquoi useState est nécessaire.
Étape 1 : la structure
function Compteur() {
let compteur = 0;
return (
<div>
<p>Valeur : {compteur}</p>
<button>Incrémenter</button>
</div>
);
}
Un composant statique. Le bouton existe mais ne fait rien.
Étape 2 : ajouter un gestionnaire d’événement
function Compteur() {
let compteur = 0;
const handleClick = () => {
compteur += 1;
console.log("Valeur en mémoire :", compteur);
};
return (
<div>
<p>Valeur : {compteur}</p>
<button onClick={handleClick}>Incrémenter</button>
</div>
);
}
En testant ce code, la console affiche bien la valeur qui grimpe en mémoire, mais l’affichage à l’écran reste figé à 0.
compteur change. Pour que l’affichage suive, il faut dire explicitement à React que l’état a changé : c’est le rôle de useState.
Étape 3 : utiliser useState
import { useState } from "react";
function Compteur() {
const [compteur, setCompteur] = useState(0);
const handleClick = () => {
setCompteur(compteur + 1);
};
return (
<div>
<p>Valeur : {compteur}</p>
<button onClick={handleClick}>Incrémenter</button>
</div>
);
}
La ligne const [compteur, setCompteur] = useState(0) se lit ainsi : compteur est la valeur actuelle de l’état, setCompteur la fonction qui la met à jour, et useState(0) la valeur initiale. Quand setCompteur(...) est appelée, React redessine automatiquement le composant avec la nouvelle valeur. C’est ce nouveau rendu qui met l’affichage à jour, contrairement à l’étape 2.
Étape 4 : rendre le composant configurable
import { useState } from "react";
function Compteur({ valeurInitiale = 0, pas = 1 }) {
const [compteur, setCompteur] = useState(valeurInitiale);
return (
<div>
<p>Valeur : {compteur}</p>
<button onClick={() => setCompteur(compteur + pas)}>
Incrémenter
</button>
</div>
);
}
Deux choses à noter. Le composant accepte maintenant des props (valeurInitiale, pas) avec des valeurs par défaut. Et le gestionnaire d’événement est défini en ligne (() => setCompteur(...)), une écriture courante quand la logique est courte.
Étape 5 : utilisation
// App.jsx
import Compteur from "./Compteur";
function App() {
return (
<div>
<h1>Mon application React</h1>
<Compteur valeurInitiale={500} pas={10} />
</div>
);
}
export default App;
VérificationÀ l'étape 2, le clic incrémente bien compteur en mémoire, mais l'affichage reste à 0. Pourquoi useState corrige-t-il cela ?
Parce que React ne redessine un composant que lorsqu’il sait que son état a changé. Une variable locale (let compteur) change en mémoire, mais React ne le surveille pas, donc le rendu ne se refait pas. useState enregistre la valeur comme un état suivi : appeler setCompteur(...) signale le changement à React, qui redessine le composant avec la nouvelle valeur.
Erreurs communes à éviter
| Erreur | Symptôme | Correction |
|---|---|---|
| Composant en minuscule | Le composant ne s’affiche pas, sans erreur claire | <Compteur /> et non <compteur /> |
Modifier un prop (props.x = ...) |
Comportement imprévisible, avertissement | Les props sont en lecture seule, demander au parent |
Modifier l’état directement (compteur += 1) |
L’affichage ne se met pas à jour | Toujours passer par setCompteur(...) |
Oublier key dans un .map() |
Avertissement, bogues de rendu | Ajouter key={item.id} |
| Plusieurs éléments sans racine | Erreur de compilation | Englober dans <>...</> ou un <div> |
Appeler setXxx dans le corps du composant |
Boucle infinie de rendus | Ne mettre à jour l’état que dans un gestionnaire ou un useEffect |
Récapitulatif
| Idée | Point clé |
|---|---|
| Composant | Une fonction qui retourne du JSX, nommée avec une majuscule |
| Composition | Des composants dans des composants, pour bâtir l’interface |
| Props | Données du parent vers l’enfant, immuables |
État (useState) |
Mémoire d’un composant ; la modifier déclenche un nouveau rendu |
| Listes | Rendues avec .map() et une key unique par élément |
| Fragment | <>...</> groupe sans ajouter de balise au DOM |