Chapitre 319 minIntermédiaire
Les props : passer des données à un composant
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- passer des données d'un composant parent à un enfant ;
- typer les props avec TypeScript et donner des valeurs par défaut ;
- faire remonter un événement de l'enfant au parent par une fonction ;
- écrire un composant qui affiche le contenu reçu dans children.
4 exercices corrigés1 schéma
Les props (pour properties) sont les données qu'un composant parent transmet à un composant enfant, comme des attributs sur une balise : <BadgeEtat etat="VA" />. Pour l'enfant, ce sont les paramètres de sa fonction. Ce chapitre montre comment déclarer, typer avec TypeScript, lire et transmettre des props, y compris des fonctions et du contenu (children), et pourquoi un composant ne doit jamais modifier les siennes.
Passer et lire une prop
Le parent écrit les props comme des attributs :
<BadgeEtat etat="VA" />
<CarteFiche fiche={fiche} />
<ListeFrais frais={fiche.fraisHorsForfait} modifiable={true} />
React les rassemble dans un objet qu'il passe en premier paramètre à la fonction du composant. On le déstructure directement dans la signature :
// src/components/BadgeEtat.tsx
import type { EtatFiche } from '../types';
const LIBELLES: Record<EtatFiche, string> = {
CR: 'Saisie en cours',
CL: 'Clôturée',
VA: 'Validée',
MP: 'Mise en paiement',
};
type BadgeEtatProps = {
etat: EtatFiche;
};
export function BadgeEtat({ etat }: BadgeEtatProps) {
return <span className={`badge badge-${etat.toLowerCase()}`}>{LIBELLES[etat]}</span>;
}
<BadgeEtat etat="VA" /> affiche alors <span class="badge badge-va">Validée</span>. La même fonction produit un badge différent selon la prop reçue : le composant est devenu réutilisable.
{ etat }: BadgeEtatProps est la déstructuration de l'objet des props. Elle équivaut à :
export function BadgeEtat(props: BadgeEtatProps) {
const etat = props.etat;
…
}
Typer les props avec TypeScript
C'est ici que TypeScript commence à vraiment servir. On décrit les données de l'application une fois, dans un fichier src/types.ts, puis on décrit les props de chaque composant avec un type.
// src/types.ts : les données échangées avec l'API d'AppliFrais
export type EtatFiche = 'CR' | 'CL' | 'VA' | 'MP';
export type FicheResume = {
id: number;
mois: string; // "2026-10"
etat: EtatFiche;
montantValide: number | null; // null tant que la fiche n'est pas validée
};
export type Frais = {
id: number;
date: string; // "2026-10-14"
libelle: string;
montant: number;
};
Les noms des champs sont ceux que renvoie l'API du cours Node.js (le détail d'une fiche) : les quatre états CR, CL, VA, MP, et des montants en nombres. Les types ne décrivent que les champs dont le front se sert ; le chapitre 7 les complète.
Quelques notations à connaître :
| Notation | Sens | Exemple |
|---|---|---|
'CR' | 'CL' | 'VA' | Union : une valeur parmi celles-là | etat: EtatFiche refuse 'XX' |
number | null | Un nombre ou rien | montantValide |
nom?: string | Prop facultative : string ou undefined | titre?: string |
Record<EtatFiche, string> | Un objet avec une clé par valeur de EtatFiche | LIBELLES : TypeScript signale un état oublié |
Frais[] | Un tableau de Frais | frais: Frais[] |
import type { … } | Importer seulement des types (effacés à la compilation) | import type { Frais } from '../types' |
Une fois les props typées, l'éditeur complète les noms et signale les erreurs dans le composant parent :
<BadgeEtat /> // Erreur : la propriété 'etat' est manquante
<BadgeEtat etat="Validée" /> // Erreur : '"Validée"' n'est pas assignable au type 'EtatFiche'
<BadgeEtat etat="VA" /> // Correct
Des props de tous types
Une prop peut contenir n'importe quelle valeur JavaScript :
<CarteFiche
fiche={fiche} // un objet
compacte // un booléen : écrire la prop seule vaut true
nbMax={5} // un nombre, entre accolades
titre="Octobre" // une chaîne, entre guillemets
onSelection={(id) => voir(id)} // une fonction
/>
Et CarteFiche devient enfin utile, avec des données qui viennent de son parent :
// src/components/CarteFiche.tsx
import { BadgeEtat } from './BadgeEtat';
import { formatMois, formatMontant } from '../format';
import type { FicheResume } from '../types';
type CarteFicheProps = {
fiche: FicheResume;
};
export function CarteFiche({ fiche }: CarteFicheProps) {
return (
<article className="carte-fiche">
<h2>Fiche de frais – {formatMois(fiche.mois)}</h2>
<BadgeEtat etat={fiche.etat} />
{fiche.montantValide !== null && <p>Montant validé : {formatMontant(fiche.montantValide)}</p>}
</article>
);
}
Les fonctions de mise en forme sont rangées à part, dans src/format.ts : ce sont des fonctions JavaScript ordinaires, faciles à tester (chapitre 13).
// src/format.ts
const euros = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' });
const moisAnnee = new Intl.DateTimeFormat('fr-FR', { month: 'long', year: 'numeric' });
export function formatMontant(montant: number): string {
return euros.format(montant); // 389.2 -> "389,20 €"
}
// "2026-10-14" -> "14/10/2026", sans passer par Date (pas de décalage de fuseau horaire)
export function formatDate(date: string): string {
return date.split('-').reverse().join('/');
}
// "2026-10" -> "octobre 2026"
export function formatMois(mois: string): string {
const [annee, numero] = mois.split('-').map(Number);
return moisAnnee.format(new Date(annee, numero - 1, 1)); // les mois de Date commencent à 0
}
Les valeurs par défaut
Une prop facultative reçoit sa valeur par défaut dans la déstructuration, avec = :
type ListeFraisProps = {
frais: Frais[];
modifiable?: boolean; // facultative
titre?: string; // facultative
};
export function ListeFrais({ frais, modifiable = false, titre = 'Frais hors forfait' }: ListeFraisProps) {
…
}
<ListeFrais frais={liste} /> utilise alors modifiable = false et le titre par défaut.
Les props sont en lecture seule
Un composant ne modifie jamais ses props. Elles appartiennent au parent qui les a créées : les modifier dans l'enfant rendrait les données incohérentes, et React ne saurait pas qu'il faut redessiner.
function CarteFiche({ fiche }: CarteFicheProps) {
fiche.etat = 'CL'; // INTERDIT : on modifie un objet du parent
…
}
Si une donnée doit changer, c'est son propriétaire qui la change, avec son état (chapitre 4). L'enfant peut le lui demander en appelant une fonction reçue en prop.
Passer une fonction : l'enfant prévient le parent
Les données descendent du parent vers l'enfant ; les événements remontent par des fonctions. Le parent passe une fonction en prop, l'enfant l'appelle au bon moment :
// src/components/FiltreEtat.tsx
import type { EtatFiche } from '../types';
type FiltreEtatProps = {
valeur: EtatFiche | '';
onChange: (etat: EtatFiche | '') => void; // une fonction qui reçoit l'état choisi
};
export function FiltreEtat({ valeur, onChange }: FiltreEtatProps) {
return (
<p>
<label htmlFor="filtre-etat">État</label>{' '}
<select
id="filtre-etat"
value={valeur}
onChange={(e) => onChange(e.target.value as EtatFiche | '')}
>
<option value="">Toutes les fiches</option>
<option value="CR">Saisie en cours</option>
<option value="CL">Clôturées</option>
<option value="VA">Validées</option>
<option value="MP">Mises en paiement</option>
</select>
</p>
);
}
FiltreEtat ne sait pas ce que le parent fera du choix : filtrer une liste, changer l'URL, appeler l'API. Il se contente de le signaler. Par convention, une prop qui reçoit une fonction appelée lors d'un événement s'appelle onQuelqueChose. Le parent qui utilise ce filtre est au chapitre 11.
{' '} insère une espace entre le label et le select : le JSX supprime les retours à la ligne entre deux balises.
Le contenu entre les balises : children
Ce que tu écris entre la balise ouvrante et la balise fermante d'un composant arrive dans une prop spéciale, children :
import type { ReactNode } from 'react';
type EncadreProps = {
titre: string;
children: ReactNode; // n'importe quoi d'affichable : texte, éléments, composants
};
export function Encadre({ titre, children }: EncadreProps) {
return (
<section className="encadre">
<h2>{titre}</h2>
{children}
</section>
);
}
// Utilisation
<Encadre titre="Règles de remboursement">
<p>Les repas sont remboursés jusqu'à 25 € sur justificatif.</p>
<p>Les frais doivent être saisis avant le 10 du mois suivant.</p>
</Encadre>
children permet d'écrire des composants « contenants » (un encadré, une fenêtre modale, une mise en page) qui ne savent pas à l'avance ce qu'ils contiendront. Layout, au chapitre 10, suit la même idée avec le composant Outlet du routeur.
Transmettre toutes les props
Parfois, un composant transmet la plupart de ses props à un élément HTML. La syntaxe de décomposition {...reste} les passe toutes :
import type { ComponentProps } from 'react';
type BoutonProps = ComponentProps<'button'> & { variante?: 'principal' | 'danger' };
export function Bouton({ variante = 'principal', className = '', ...reste }: BoutonProps) {
return <button className={`bouton bouton-${variante} ${className}`} {...reste} />;
}
<Bouton variante="danger" type="button" onClick={supprimer} disabled={enCours}>Supprimer</Bouton>
ComponentProps<'button'> reprend tous les attributs possibles d'un <button> (type, onClick, disabled, aria-label…) : le composant les accepte et les transmet sans les énumérer. Garde cette technique pour de petits composants d'interface ; pour un composant métier comme CarteFiche, des props explicites sont plus lisibles.
Exercices
Exercice 1 sur 4 Écrire un composant avec props Réussi À revoir
Écris le composant MontantFrais qui reçoit un montant (nombre) et un plafond facultatif (nombre, 25 par défaut). Il affiche le montant formaté en euros ; si le montant dépasse le plafond, il ajoute « (au-delà du plafond) ». Type ses props.
Voir la réponseMasquer la réponse de l'exercice 1
import { formatMontant } from '../format';
type MontantFraisProps = {
montant: number;
plafond?: number;
};
export function MontantFrais({ montant, plafond = 25 }: MontantFraisProps) {
return (
<span>
{formatMontant(montant)}
{montant > plafond && ' (au-delà du plafond)'}
</span>
);
}
<MontantFrais montant={31.5} /> affiche « 31,50 € (au-delà du plafond) » ; <MontantFrais montant={31.5} plafond={40} /> affiche « 31,50 € ».
Tu avais trouvé ?
Exercice 2 sur 4 Lire les erreurs de TypeScript Réussi À revoir
Avec le BadgeEtat et le CarteFiche de ce chapitre, explique chaque erreur signalée par TypeScript.
<BadgeEtat etat="cr" /><CarteFiche fiche={{ id: 12, mois: '2026-10', etat: 'CR' }} /><CarteFiche fiche={fiche} couleur="bleu" />
Voir la réponseMasquer la réponse de l'exercice 2
'cr'n'est pas dans l'unionEtatFiche: les codes sont en majuscules ('CR'). Sans TypeScript, le badge aurait affichéundefined, carLIBELLES['cr']n'existe pas.- Il manque la propriété
montantValide, obligatoire dansFicheResume:nulldoit être écrit explicitement pour une fiche qui n'est pas encore validée. couleurn'existe pas dansCarteFicheProps: TypeScript refuse une prop inconnue, ce qui attrape les fautes de frappe (fihce=).
Tu avais trouvé ?
Exercice 3 sur 4 Remonter une information Réussi À revoir
Écris un composant BoutonSupprimer qui reçoit le libellé d'un frais et une fonction onConfirmer. Au clic, il demande une confirmation avec window.confirm('Supprimer « Taxi » ?') et n'appelle onConfirmer que si l'utilisateur accepte.
Voir la réponseMasquer la réponse de l'exercice 3
type BoutonSupprimerProps = {
libelle: string;
onConfirmer: () => void;
};
export function BoutonSupprimer({ libelle, onConfirmer }: BoutonSupprimerProps) {
function cliquer() {
if (window.confirm(`Supprimer « ${libelle} » ?`)) {
onConfirmer();
}
}
return <button type="button" onClick={cliquer}>Supprimer</button>;
}
// Dans le parent :
<BoutonSupprimer libelle={f.libelle} onConfirmer={() => supprimer(f.id)} />
Le bouton ne sait pas comment on supprime : il demande confirmation et prévient le parent. Le chapitre 14 montre une boîte de dialogue plus accessible que window.confirm.
Tu avais trouvé ?
Exercice 4 sur 4 Mettre à jour un ancien composant Réussi À revoir
Ce composant vient d'un ancien projet. Réécris-le pour React 19 avec TypeScript.
import PropTypes from 'prop-types';
function Alerte(props) {
return <div className={'alerte alerte-' + props.niveau}>{props.message}</div>;
}
Alerte.propTypes = { message: PropTypes.string.isRequired, niveau: PropTypes.string };
Alerte.defaultProps = { niveau: 'info' };
Voir la réponseMasquer la réponse de l'exercice 4
type AlerteProps = {
message: string;
niveau?: 'info' | 'succes' | 'erreur';
};
export function Alerte({ message, niveau = 'info' }: AlerteProps) {
return <div className={`alerte alerte-${niveau}`}>{message}</div>;
}
propTypes et defaultProps sont ignorés par React 19 pour les composants fonctions : avec l'ancien code, niveau vaudrait undefined et la classe deviendrait alerte-undefined. Le type remplace la vérification, et le = 'info' de la déstructuration remplace defaultProps. L'union 'info' | 'succes' | 'erreur' est plus stricte que PropTypes.string.
Tu avais trouvé ?