Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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 :

tsx
<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 :

tsx
// 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 à :

tsx
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.

ts
// 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 :

NotationSensExemple
'CR' | 'CL' | 'VA'Union : une valeur parmi celles-làetat: EtatFiche refuse 'XX'
number | nullUn nombre ou rienmontantValide
nom?: stringProp facultative : string ou undefinedtitre?: string
Record<EtatFiche, string>Un objet avec une clé par valeur de EtatFicheLIBELLES : TypeScript signale un état oublié
Frais[]Un tableau de Fraisfrais: 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 :

tsx
<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 :

tsx
<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 :

tsx
// 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).

ts
// 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 = :

tsx
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.

tsx
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 :

tsx
// 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.

AppLayoutPageFichespossède l'état du filtrevaleuronChangeonChange('VA')FiltreEtatla liste déroulanteficheCarteFicheCarteFicheCarteFicheetatBadgeEtatBadgeEtatBadgeEtatles données descendent par les propsl'enfant prévient le parent en appelant une fonction reçue en prop
Les données descendent par les props, les événements remontent par des fonctions

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 :

tsx
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 :

tsx
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
tsx
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.

  1. <BadgeEtat etat="cr" />
  2. <CarteFiche fiche={{ id: 12, mois: '2026-10', etat: 'CR' }} />
  3. <CarteFiche fiche={fiche} couleur="bleu" />
Voir la réponseMasquer la réponse de l'exercice 2
  1. 'cr' n'est pas dans l'union EtatFiche : les codes sont en majuscules ('CR'). Sans TypeScript, le badge aurait affiché undefined, car LIBELLES['cr'] n'existe pas.
  2. Il manque la propriété montantValide, obligatoire dans FicheResume : null doit être écrit explicitement pour une fiche qui n'est pas encore validée.
  3. couleur n'existe pas dans CarteFicheProps : 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
tsx
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.

jsx
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
tsx
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é ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : useState et événements : rendre l'interface interactiveRevoir le sommaire du cours