Retour au cours

frontend / react

Composants et props

Leçon 21 exercice

Explication

Ce que vous allez apprendre

  • Écrire un composant fonctionnel qui reçoit et déstructure ses props
  • Utiliser children pour créer un composant conteneur générique
  • Donner une valeur par défaut à une prop optionnelle
  • Comprendre pourquoi les props sont en lecture seule et ce qu'il faut faire à la place
  • Repérer et éviter le prop-drilling avant qu'il ne devienne un problème

Dans quel contexte ?

Dans src/components/UserCard.jsx, un développeur doit afficher soit un badge "Admin", soit rien du tout, selon le rôle de l'utilisateur reçu depuis l'API /api/users/:id. Plutôt que de créer deux composants presque identiques (UserCardAdmin et UserCardStandard), il crée un seul composant UserCard qui accepte une prop role et adapte son rendu. Trois écrans plus tard (liste, profil, page d'équipe), ce même composant est réutilisé tel quel, seules les props changent.

Étape 1 : un composant, une fonction

Un composant React est simplement une fonction JavaScript qui retourne du JSX, c'est-à-dire une description de ce que l'utilisateur doit voir à l'écran.

Étape 2 : l'idée centrale de React

L'idée centrale de React est de découper une interface complexe en petits morceaux indépendants et réutilisables : un bouton, une carte produit, un formulaire. Chaque composant s'assemble ensuite avec d'autres, comme des briques de Lego.

Étape 3 : le problème que résolvent les props

Un composant isolé n'a d'intérêt que s'il peut être personnalisé selon le contexte : un même composant CarteProduit doit pouvoir afficher des produits différents.

Étape 4 : comment on personnalise un composant

C'est le rôle des props, abréviation de "properties" : des paramètres transmis à un composant, exactement comme des arguments passés à une fonction. Elles évitent de dupliquer du code pour chaque variante de données.

Étape 5 : le cas particulier de children

Les props sont reçues comme un unique objet, souvent déstructuré directement ({ nom, prix }). Un cas particulier important est children : il représente tout ce qui est placé entre la balise ouvrante et fermante d'un composant, ce qui permet de créer des conteneurs génériques.

Étape 6 : le piège à connaître, les props sont immuables

Il reste une règle stricte : les props sont en lecture seule, un composant ne doit jamais les modifier lui-même. Si une donnée doit évoluer dans le temps, ce n'est plus une prop qu'il faut, mais un state, vu dans la leçon suivante.

Piège fréquent

props.nom = "autre chose" à l'intérieur d'un composant ne provoque pas d'erreur visible immédiatement, mais casse le modèle mental de React et provoque des bugs difficiles à tracer, car le parent continue de croire que la donnée n'a pas changé.

SituationBon outilPourquoi
Donnée transmise par le parent, fixePropLe composant n'a pas à la modifier
Donnée qui change suite à une interactionState (useState)Doit déclencher un re-rendu
Donnée partagée par de nombreux composants éloignésContextÉvite le prop-drilling

Un dernier piège, le prop-drilling

Autre piège fréquent : faire transiter une prop à travers des composants intermédiaires qui n'en ont eux-mêmes pas besoin, juste pour atteindre un enfant plus profond. Ce "prop-drilling" reste acceptable ponctuellement, mais l'API Context, abordée plus loin dans ce cours, est conçue pour le résoudre à grande échelle.

Et ensuite ?

Une fois props et composants compris, la prochaine étape consiste à donner à un composant sa propre mémoire qui évolue dans le temps, avec le state.

Commandes & code

Composants et props

Un composant est une fonction qui reçoit des props et retourne du JSX. C'est l'unité de base de toute app React.

jsx
// Composant fonctionnel avec props typées (destructuration directe)
function CarteProduit({ nom, prix, image, enPromo }) {
    return (
        <article className="carte-produit">
            <img src={image} alt={nom} />
            <h3>{nom}</h3>
            <p>{prix}€ {enPromo && <span className="badge">Promo</span>}</p>
        </article>
    );
}

// Utilisation : chaque attribut JSX devient une clé de l'objet props
function CataloguePage() {
    return (
        <div className="grille">
            <CarteProduit nom="Chaise ergonomique" prix={129} image="/chaise.jpg" enPromo />
            <CarteProduit nom="Bureau réglable" prix={349} image="/bureau.jpg" enPromo={false} />
        </div>
    );
}
jsx
// props.children : le contenu placé ENTRE les balises ouvrantes/fermantes
function Panneau({ titre, children }) {
    return (
        <div className="panneau">
            <h2>{titre}</h2>
            <div className="panneau-contenu">{children}</div>
        </div>
    );
}

function App() {
    return (
        <Panneau titre="Statistiques">
            <p>128 utilisateurs actifs</p>
            <p>42 cours publiés</p>
        </Panneau>
    );
}
jsx
// Valeurs par défaut et props en lecture seule
function Bouton({ label, variante = "primaire", onClick, disabled = false }) {
    // props sont IMMUABLES : jamais de props.label = "autre chose"
    return (
        <button
            className={`bouton bouton--${variante}`}
            onClick={onClick}
            disabled={disabled}
        >
            {label}
        </button>
    );
}

// Composition : préférer children/props à l'héritage (React n'a pas d'héritage de composants)
function CarteAvecActions({ children, actions }) {
    return (
        <div className="carte">
            <div className="carte-corps">{children}</div>
            <div className="carte-actions">{actions}</div>
        </div>
    );
}

function ExempleComposition() {
    return (
        <CarteAvecActions actions={<Bouton label="Supprimer" variante="danger" />}>
            <p>Contenu de la carte</p>
        </CarteAvecActions>
    );
}
jsx
// Spread de props : pratique, mais à utiliser avec discernement (perte de lisibilité si abusé)
function Champ(props) {
    return <input {...props} className="champ-standard" />;
}

// Prop-drilling : le problème que Context (leçon dédiée) résout à grande échelle
function App() {
    const theme = "sombre";
    return <Layout theme={theme} />;
}
function Layout({ theme }) {
    return <Sidebar theme={theme} />; // theme transite juste pour atteindre un enfant plus profond
}
function Sidebar({ theme }) {
    return <div className={`sidebar sidebar--${theme}`}>...</div>;
}

Résumé

  • Les props sont immuables côté composant : elles ne se modifient jamais directement.
  • children porte le contenu imbriqué entre les balises d'ouverture/fermeture du composant.
  • Composition (children, props de type élément) remplace l'héritage, absent du modèle React.
  • Le prop-drilling profond est un signal qu'un Context ou un state manager devient pertinent.

Exercices pratiques

1 disponible
1

Mission : dépolluer un composant qui modifie ses props

Objectif : Diagnostiquer un bug causé par la mutation directe d'une prop et repérer un prop-drilling à corriger.

Contexte

Un développeur junior a écrit ce composant qui, après un clic sur "Appliquer la remise", n'affiche jamais le nouveau prix à l'écran malgré l'absence d'erreur dans la console :

jsx
function CarteProduit({ prix }) {
    function appliquerRemise() {
        prix = prix * 0.9; // aucune erreur visible, mais l'affichage ne bouge jamais
    }
    return (
        <div>
            <p>{prix}€</p>
            <button onClick={appliquerRemise}>Appliquer la remise</button>
        </div>
    );
}

Ce même fichier fait aussi transiter une prop theme à travers trois composants intermédiaires (Layout, Sidebar, Menu) qui ne l'utilisent jamais eux-mêmes, juste pour l'atteindre plus bas.

Résoudre l’exercice →