React : composants et état
JSX, composants, props, état avec useState, effets avec useEffect, listes, formulaires et remontée d'état. La bibliothèque qui structure la majorité des interfaces web modernes.
À la fin du cours, tu sais
- Comprendre l'approche déclarative de React et le concept de composant
- Écrire du JSX et y injecter des expressions JavaScript
- Créer des composants réutilisables et leur passer des props typées
- Afficher des listes et faire du rendu conditionnel
- Gérer l'état local d'un composant avec useState
- Synchroniser un composant avec l'extérieur grâce à useEffect
- Construire des formulaires contrôlés et faire remonter l'état
Prérequis
- Maîtriser les bases de JavaScript, surtout map, filter et les fonctions fléchées
- Des notions de TypeScript aident à typer les props (voir le cours dédié)
Étape 1 sur 11 : Pourquoi React
Chapitre 1
Pourquoi React
Manipuler le DOM à la main devient vite ingérable. React propose une autre façon de penser l'interface : tu décris ce que tu veux voir, React se charge de l'afficher.
Au chapitre du DOM en JavaScript, tu as vu comment sélectionner un élément et le modifier à la main : querySelector, textContent, appendChild. Ça fonctionne pour une petite page, mais dès qu'une interface devient riche (des dizaines d'éléments qui changent selon les données), gérer ces modifications manuellement devient un cauchemar : on oublie de mettre à jour un endroit, l'affichage se désynchronise des données, les bugs se multiplient. React est né chez Facebook pour résoudre exactement ce problème.
React est une bibliothèque JavaScript (pas un framework complet) dédiée à la construction d'interfaces. Son idée fondatrice est le passage d'une approche impérative (« va chercher cet élément et change son texte ») à une approche déclarative : tu décris à quoi l'interface doit ressembler pour un état donné des données, et React se charge de mettre le DOM à jour pour correspondre. Tu ne dis plus comment modifier la page, tu dis quoi afficher.
Impératif contre déclaratif
// Impératif (JavaScript pur) : tu décris chaque étape de la modification
const compteur = document.querySelector("#compteur");
let valeur = 0;
document.querySelector("#btn").addEventListener("click", () => {
valeur++;
compteur.textContent = "Compteur : " + valeur; // mise à jour manuelle
});// Déclaratif (React) : tu décris le résultat, React s'occupe du DOM
function Compteur() {
const [valeur, setValeur] = useState(0);
return (
<div>
<p>Compteur : {valeur}</p>
<button onClick={() => setValeur(valeur + 1)}>Incrémenter</button>
</div>
);
}Ne t'inquiète pas de la syntaxe pour l'instant, on la décortique dans les chapitres suivants. Retiens la différence de philosophie : dans la version React, tu ne touches jamais le DOM directement. Tu décris que l'affichage montre valeur, et quand valeur change, React recalcule ce qu'il faut afficher et met le DOM à jour pour toi. C'est ce qui rend les interfaces complexes gérables.
Le composant, brique de base
Tout, en React, est composant : un bout d'interface autonome et réutilisable, décrit par une fonction JavaScript qui renvoie ce qu'il faut afficher. Un bouton, une carte produit, un formulaire, une page entière : autant de composants, qu'on assemble comme des briques de Lego. Cette découpe en composants réutilisables est le deuxième pilier de React, avec l'approche déclarative.
React, et après ?
Vrai ou faux ?
Avec React, on manipule directement le DOM avec querySelector et textContent pour mettre à jour l'affichage.
Chapitre 2
JSX : décrire l'interface
JSX est cette syntaxe qui ressemble à du HTML au milieu du JavaScript. Elle paraît étrange au début, mais devient vite naturelle.
Le JSX (JavaScript XML) est la syntaxe qui permet d'écrire, au sein de ton code JavaScript, quelque chose qui ressemble à du HTML. Ce n'est ni du HTML ni une chaîne de caractères : c'est une façon d'exprimer ce que le composant doit afficher, qui sera transformée en JavaScript lors de la compilation. Ça surprend au premier regard (« du HTML dans du JS ? »), mais on s'y fait très vite, et ça rend le code d'interface bien plus lisible.
function Bienvenue() {
return (
<div>
<h1>Bonjour !</h1>
<p>Ceci est du JSX, pas du HTML.</p>
</div>
);
}Injecter du JavaScript avec les accolades
La vraie puissance du JSX, c'est qu'on peut y insérer n'importe quelle expression JavaScript entre accolades { }. Une variable, un calcul, un appel de fonction : tout ce qui produit une valeur peut être injecté dans l'affichage. C'est le pont entre tes données et ce qui s'affiche.
function Profil() {
const prenom = "Alice";
const age = 30;
return (
<div>
<h1>Bonjour {prenom}</h1>
<p>Dans 5 ans, tu auras {age + 5} ans.</p>
<p>{prenom.toUpperCase()}</p>
</div>
);
}Les règles du JSX à connaître
- Un seul élément parent : un composant doit renvoyer un seul élément racine. Si besoin, enveloppe dans un fragment
<>...</>. classNameau lieu declass:classest un mot réservé en JavaScript, on utiliseclassNamepour les classes CSS.- Les balises se ferment toujours :
<img />,<br />, même les balises seules. - camelCase pour les attributs :
onClick,onChange,htmlForau lieu de leurs équivalents HTML.
function Carte() {
return (
<> {/* fragment : un parent invisible */}
<img src="/photo.jpg" alt="Une photo" />
<p className="legende">Texte avec une classe CSS</p>
</>
);
}L'erreur du débutant : oublier le parent unique
<>...</> : un conteneur invisible qui n'ajoute rien au DOM mais satisfait la règle du parent unique.Vrai ou faux ?
En JSX, on écrit class="btn" comme en HTML pour appliquer une classe CSS.
Chapitre 3
Les composants et les props
Un composant devient vraiment utile quand on peut le personnaliser. Les props sont les paramètres qu'on lui passe, comme les arguments d'une fonction.
Un composant sans personnalisation afficherait toujours la même chose. Les props (abréviation de properties) sont les données qu'un composant parent transmet à un composant enfant, exactement comme on passe des arguments à une fonction. Elles rendent un composant réutilisable : un même composant Carte peut afficher des dizaines de produits différents selon les props qu'il reçoit.
// Le composant reçoit ses props en argument
function Salutation(props: { prenom: string }) {
return <h1>Bonjour {props.prenom} !</h1>;
}
// On l'utilise en passant les props comme des attributs
function App() {
return (
<div>
<Salutation prenom="Alice" />
<Salutation prenom="Bob" />
</div>
);
}Le composant Salutation reçoit un objet props contenant un prenom, qu'il affiche. On le réutilise autant de fois qu'on veut, avec des valeurs différentes, passées comme des attributs JSX. Un composant, mille usages : c'est le principe de composition qui fait toute la force de React.
Typer et décomposer les props
// On type les props avec une interface, et on les décompose directement
interface CarteProps {
titre: string;
prix: number;
enPromo?: boolean; // optionnel
}
function Carte({ titre, prix, enPromo }: CarteProps) {
return (
<article>
<h2>{titre}</h2>
<p>{prix} €</p>
{enPromo && <span>En promotion</span>}
</article>
);
}En pratique, on type les props avec une interface (ton cours TypeScript prend tout son sens ici) et on les décompose directement dans les paramètres : { titre, prix } au lieu de props.titre, props.prix. C'est l'écriture standard que tu verras partout. L'interface documente ce que le composant attend, et l'éditeur t'autocomplète les props à l'usage.
La prop spéciale children
// children : le contenu placé entre les balises du composant
function Encadre({ children }: { children: React.ReactNode }) {
return <div className="encadre">{children}</div>;
}
function App() {
return (
<Encadre>
<p>Ce paragraphe est passé en children.</p>
</Encadre>
);
}Les props sont en lecture seule
Vrai ou faux ?
Un composant enfant peut modifier les props que son parent lui a transmises.
Chapitre 4
Listes et rendu conditionnel
Afficher une liste d'éléments et montrer ou cacher des morceaux selon les données : deux besoins constants, résolus avec du JavaScript que tu connais déjà.
Une interface affiche presque toujours des listes : des articles, des messages, des résultats de recherche. En React, on génère une liste d'éléments JSX en appliquant map sur un tableau de données. C'est exactement le map de ton cours JavaScript, mais qui renvoie du JSX au lieu de valeurs simples.
function ListeFruits() {
const fruits = ["pomme", "banane", "cerise"];
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}On parcourt le tableau avec map, et pour chaque élément on renvoie un <li>. Remarque l'attribut key : React en a besoin pour identifier chaque élément de la liste de façon unique, afin de mettre à jour efficacement le DOM quand la liste change. Une clé doit être stable et unique : l'identifiant d'un élément est idéal ; l'index de la boucle est un dernier recours.
N'oublie jamais la key
key, React affiche un avertissement dans la console et peut se tromper en mettant à jour la liste (mauvais élément supprimé, état mélangé). Utilise un identifiant unique et stable, pas l'index du tableau si la liste peut être réordonnée ou filtrée. C'est un point que les recruteur·euses vérifient systématiquement.Le rendu conditionnel
Afficher un élément seulement dans certains cas est tout aussi courant. Deux techniques, toutes deux issues du JavaScript pur : l'opérateur && pour « afficher si », et l'opérateur ternaire pour « ceci ou cela ».
function Statut({ connecte, nom }: { connecte: boolean; nom: string }) {
return (
<div>
{/* && : affiche le message SEULEMENT si connecte est vrai */}
{connecte && <p>Bienvenue {nom} !</p>}
{/* ternaire : affiche l'un OU l'autre */}
{connecte ? <button>Déconnexion</button> : <button>Connexion</button>}
</div>
);
}{connecte && <p>...</p>} affiche le paragraphe uniquement si connecte est vrai (rappelle-toi les valeurs truthy du cours JavaScript). Le ternaire {connecte ? A : B} choisit entre deux affichages. Ces deux motifs couvrent l'immense majorité des besoins de rendu conditionnel. Pas de nouvelle syntaxe React à apprendre : juste du JavaScript placé dans des accolades JSX.
Vrai ou faux ?
L'attribut key dans une liste React est optionnel et n'a pas d'impact réel.
Chapitre 5
L'état avec useState
L'état (state) est la donnée qui change au fil des interactions et qui, en changeant, redéclenche l'affichage. C'est le cœur battant de React.
Les props viennent du parent et ne changent pas depuis l'intérieur. Mais un composant a souvent besoin de données qui lui sont propres et qui évoluent : le contenu d'un champ, le fait qu'un menu soit ouvert, la valeur d'un compteur. C'est l'état (state). En React, on le gère avec le hook useState. Un hook est une fonction spéciale de React dont le nom commence par use.
import { useState } from "react";
function Compteur() {
// useState renvoie [la valeur, la fonction pour la modifier]
const [compte, setCompte] = useState(0);
return (
<div>
<p>Compteur : {compte}</p>
<button onClick={() => setCompte(compte + 1)}>+1</button>
</div>
);
}useState(0) déclare un état initialisé à 0 et renvoie un tableau de deux éléments, qu'on décompose : la valeur actuelle (compte) et une fonction pour la modifier (setCompte). La règle d'or : on ne réassigne jamais l'état directement (compte = 5 ne marche pas), on passe toujours par la fonction setCompte. C'est elle qui prévient React qu'il doit recalculer l'affichage.
Le re-render : la clé de tout
Voici le mécanisme central de React, à bien intégrer : quand tu appelles setCompte, React ré-exécute la fonction du composant avec la nouvelle valeur, et met à jour l'affichage. C'est ce qu'on appelle un re-render. Tu ne mets jamais à jour le DOM toi-même : tu changes l'état, et l'affichage suit automatiquement. Toute l'interface découle de l'état à un instant donné.
Mettre à jour sans muter
function ListeTaches() {
const [taches, setTaches] = useState<string[]>(["Apprendre React"]);
function ajouter(nouvelle: string) {
// On crée un NOUVEAU tableau, on ne modifie pas l'ancien
setTaches([...taches, nouvelle]);
}
return (
<ul>
{taches.map((t) => (
<li key={t}>{t}</li>
))}
</ul>
);
}Ne mute jamais l'état directement
taches.push(nouvelle) modifie le tableau existant : React ne détecte pas le changement et l'affichage ne se met pas à jour. Il faut créer une nouvelle valeur avec la propagation ([...taches, nouvelle]) et la passer à setTaches. Même principe pour les objets : { ...objet, cle: valeur }. C'est le piège numéro un du débutant en React.Vrai ou faux ?
Pour modifier un état déclaré avec const [n, setN] = useState(0), on peut écrire n = 5 directement.
Chapitre 6
Les effets avec useEffect
Certaines actions doivent se produire en marge du rendu : aller chercher des données, s'abonner à un événement. C'est le rôle du hook useEffect.
Le rendu d'un composant doit rester « pur » : à partir des props et de l'état, il produit du JSX, sans effet de bord. Mais une application a besoin de faire des choses en marge : appeler une API, mettre à jour le titre de l'onglet, démarrer un minuteur. Ces actions s'appellent des effets de bord, et on les déclenche avec le hook useEffect. Il permet de synchroniser ton composant avec quelque chose d'extérieur.
import { useState, useEffect } from "react";
function ProfilUtilisateur({ id }: { id: number }) {
const [utilisateur, setUtilisateur] = useState<Utilisateur | null>(null);
useEffect(() => {
// Cet effet se lance après le rendu, et à chaque changement de 'id'
fetch(`/api/users/${id}`)
.then((r) => r.json())
.then((data) => setUtilisateur(data));
}, [id]); // tableau de dépendances : relance si 'id' change
if (!utilisateur) return <p>Chargement...</p>;
return <h1>{utilisateur.nom}</h1>;
}
interface Utilisateur {
nom: string;
}useEffect prend deux arguments : une fonction (l'effet à exécuter) et un tableau de dépendances. Ce tableau contrôle quand l'effet se relance : à chaque fois qu'une des valeurs listées change. Ici [id] signifie « relance le fetch quand id change ». C'est le motif standard pour charger des données au montage d'un composant et à chaque changement de paramètre.
Le tableau de dépendances, à ne pas rater
[id]: l'effet se relance à chaque fois queidchange (le cas le plus courant).[](vide) : l'effet se lance une seule fois, au montage du composant.- Pas de tableau du tout : l'effet se relance à chaque rendu. Rarement voulu, souvent un bug.
Le nettoyage (cleanup)
useEffect(() => {
const timer = setInterval(() => console.log("tic"), 1000);
// La fonction renvoyée nettoie l'effet (au démontage ou avant relance)
return () => clearInterval(timer);
}, []);Un effet qui met en place quelque chose de persistant (un minuteur, un abonnement) doit le nettoyer pour éviter les fuites. Il suffit de renvoyer une fonction depuis l'effet : React l'appellera quand le composant disparaît ou avant de relancer l'effet. Ici on arrête le minuteur avec clearInterval. Oublier le nettoyage laisse tourner des minuteurs fantômes, une source de bugs subtils.
useEffect n'est pas pour tout
useEffect sert à se synchroniser avec l'extérieur (API, DOM, minuteurs). Pour transformer des données à afficher, tu n'as pas besoin d'un effet : calcule directement pendant le rendu. Bien juger quand un effet est nécessaire est une marque de maturité, appréciée en entretien.🧭 Ton composant fait des requêtes en boucle infinie
Tu charges des données dans un <code>useEffect</code>, mais la console montre des centaines de requêtes qui s'enchaînent sans fin. La page rame. Que fais-tu ?
Ton effet appelle fetch puis setDonnees(...). Quelle est ta première hypothèse sur la cause de la boucle ?
Pour aller plus loin
- La documentation officielle de React, en français · react.dev (nouvel onglet)
- Penser en React, un guide fondamental · react.dev (nouvel onglet)
- Les hooks expliqués (useState, useEffect...) · react.dev (nouvel onglet)
- Prérequis utile : TypeScript pour typer tes props · Ce site (nouvel onglet)
Chapitre 7
Formulaires et remontée d'état
Les formulaires sont le point de rencontre de l'état et des événements. Et pour faire communiquer des composants, on fait « remonter » l'état.
Un formulaire, c'est de l'état qui change à chaque frappe. En React, on utilise des composants contrôlés : la valeur du champ est stockée dans l'état, et le champ affiche toujours cette valeur. À chaque saisie, un gestionnaire onChange met à jour l'état, ce qui re-render le composant avec la nouvelle valeur. L'état est ainsi la source unique de vérité du formulaire.
function FormulaireRecherche() {
const [texte, setTexte] = useState("");
function onSubmit(e: React.FormEvent) {
e.preventDefault(); // empêche le rechargement de la page
console.log("Recherche :", texte);
}
return (
<form onSubmit={onSubmit}>
<input
value={texte}
onChange={(e) => setTexte(e.target.value)}
/>
<button type="submit">Chercher</button>
</form>
);
}Le champ est contrôlé par l'état : value={texte} le lie à l'état, et onChange le met à jour à chaque frappe. À la soumission, e.preventDefault() empêche le rechargement de page par défaut du navigateur, pour traiter les données en JavaScript. Ce va-et-vient valeur/onChange est le motif que tu réutiliseras pour chaque champ de formulaire.
Faire remonter l'état (lifting state up)
Que faire quand deux composants doivent partager la même donnée, ou qu'un enfant doit informer son parent d'un changement ? Comme les données ne circulent que du parent vers l'enfant, on applique la technique de la remontée d'état : on place l'état dans le parent commun, et on transmet à l'enfant une fonction (en prop) qu'il appellera pour signaler le changement. L'enfant ne modifie pas l'état lui-même, il demande au parent de le faire.
// Le parent détient l'état et passe une fonction à l'enfant
function Parent() {
const [total, setTotal] = useState(0);
return (
<div>
<p>Total : {total}</p>
<Bouton onAjout={() => setTotal(total + 1)} />
</div>
);
}
// L'enfant reçoit la fonction et l'appelle, sans connaître l'état
function Bouton({ onAjout }: { onAjout: () => void }) {
return <button onClick={onAjout}>Ajouter</button>;
}Le sens unique des données, résumé
Vrai ou faux ?
Dans un champ contrôlé, la valeur affichée par l'<input> vient de l'état du composant.
Chapitre 8
Bonnes pratiques et écosystème
Les règles à respecter pour que React fonctionne, les habitudes qui distinguent un code propre, et un aperçu de l'écosystème autour de la bibliothèque.
React repose sur quelques règles des hooks qu'il faut respecter, sous peine de bugs déroutants. La principale : on n'appelle un hook (useState, useEffect) qu'au niveau supérieur d'un composant, jamais dans une condition, une boucle ou une fonction imbriquée. React s'appuie sur l'ordre d'appel des hooks pour associer chaque état au bon composant ; le rompre casse tout.
function Composant({ connecte }: { connecte: boolean }) {
// ✅ Correct : hook au niveau supérieur, toujours appelé
const [nom, setNom] = useState("");
// ❌ Interdit : hook dans une condition
// if (connecte) {
// const [x, setX] = useState(0);
// }
return <input value={nom} onChange={(e) => setNom(e.target.value)} />;
}Les habitudes d'un bon code React
- Un composant = une responsabilité : découpe une grosse page en petits composants nommés clairement.
- Nomme tes composants en
PascalCase:CarteProduit,BarreRecherche(obligatoire, sinon React les prend pour des balises HTML). - Toujours une
keyunique et stable dans les listes, jamais l'index si la liste peut changer. - Ne mute jamais l'état : crée une nouvelle valeur avec la propagation (
...). - Type tes props avec une interface : c'est de la documentation gratuite.
L'écosystème autour de React
React ne fait que l'interface, on l'entoure donc d'outils. Un routeur (React Router) gère la navigation entre pages dans une application d'une seule page. Un framework comme Next.js ajoute le rendu côté serveur, le référencement et une structure de projet clé en main : c'est lui qui propulse ce site. Pour aller chercher et mettre en cache des données, des bibliothèques comme TanStack Query simplifient ce que tu fais aujourd'hui à la main avec useEffect et fetch.
Tu as les fondations, la suite est vaste
Vrai ou faux ?
On peut appeler useState à l'intérieur d'un if pour ne créer un état que dans certains cas.
🛠️ Exercice optionnel
Une liste de tâches complète en React
Tu vas construire un composant de liste de tâches qui rassemble tout le cours : de l'état pour les tâches et le champ de saisie, un formulaire contrôlé, un rendu de liste avec map et key, du rendu conditionnel, et des mises à jour immuables de l'état. C'est l'exercice canonique de React, celui qu'on retrouve dans tous les entretiens.
Ta mission
- État : un état
taches(tableau d'objets{ id, texte, faite }) et un étatsaisie(le contenu du champ). - Formulaire contrôlé : un
<input>lié àsaisieviavalueetonChange, dans un<form>aveconSubmit. - À la soumission (
e.preventDefault()), ajoute une tâche au tableau (mise à jour immuable avec...), puis vide la saisie. - Affichage : parcours
tachesavecmap, chaque<li>ayant unekeyunique (l'id). - Un bouton par tâche qui bascule
faite(en recréant le tableau, sans muter), et un style ou un préfixe[x]/[ ]selon l'état. - Bonus : afficher le nombre de tâches restantes avec du rendu conditionnel (« Bravo, tout est fait ! » si zéro).
Tu bloques ? Des indices, à dévoiler quand tu en as besoin.
Indice 1
Indice masqué.
Indice 2
Indice masqué.
Indice 3
Indice masqué.
✅ QCM de fin de cours
Teste tes acquis
10 questions, plusieurs réponses parfois possibles. Coche tout ce qui te semble juste, puis valide pour voir ton score et les explications.
- 1
Quelle est l'approche que React met en avant ?
- 2
Qu'est-ce que le JSX ?
- 3
Comment écrit-on une classe CSS sur un élément en JSX ?
- 4
Que sont les props d'un composant ?
- 5
Pourquoi faut-il un attribut
keyquand on affiche une liste avecmap? - 6
Que renvoie
useState(0)? - 7
Comment ajouter un élément à un tableau stocké dans l'état ?
- 8
À quoi sert le tableau de dépendances de
useEffect? - 9
Qu'est-ce qu'un composant contrôlé (formulaire) ?
- 10
Comment un composant enfant peut-il informer son parent d'un changement ?
Tu peux laisser des questions sans réponse, elles compteront comme fausses.
🎓 Attestation
Ton attestation de réussite
Termine le QCM avec au moins 80% de bonnes réponses pour débloquer ton attestation de réussite.
Tu veux ce cours pour ton équipe ?
Je peux adapter et animer ce cours pour tes formateur·ices ou tes apprenant·es, en présentiel ou en distanciel. Parlons-en pendant l'audit gratuit.
Réserver un audit gratuit →