Composition React

children, compound components & render props

Utilisez les flèches, cliquez ou glissez pour naviguer

Objectifs de la leçon

1. Comprendre la prop children

Le slot universel de React

2. Typer children correctement

ReactNode et PropsWithChildren

3. Composition vs Héritage

Le pattern React fondamental

4. Composants de layout

Card, Modal, Sidebar réutilisables

5. Compound components

Pattern avancé mais puissant

6. Slots et flexibilité

Composants ultra-personnalisables

Plan du cours

1

Rappel : les props passent des données

Mais comment passer du JSX entier Ă  un composant ?

2

La prop children

Le slot universel de React

3

Composition vs Héritage

Pourquoi en React, on ne fait JAMAIS d'héritage

4

Demo live : composants de layout

Card, Modal, Sidebar réutilisables

5

Compound components & Render props

Patterns avancés pour des composants flexibles

Rappel : les props passent des données

function Card({ title, description }) {

return (

<div>

<h2>{title}</h2>

<p>{description}</p>

</div>

);

}

Mais... comment passer du JSX entier ?

Un formulaire complexe ? Une liste d'éléments ? Un layout complet ?

La prop children

Le slot universel de React

children contient tout ce qu'on met entre les balises

ouvrante et fermante du composant

Exemple simple : un conteneur

function Container({ children }) {

return (

<div className="p-4 bg-gray-100 rounded">

{children}

</div>

);

}

Utilisation :

<Container>

<h1>Mon titre</h1>

<p>Mon contenu</p>

</Container>

children peut contenir...

Du texte

<Card>Hello!</Card>

Des éléments JSX

<Card><h1/></Card>

Des composants

<Card><Button/></Card>

Des fragments

<>...</>

MĂŞme des fonctions (render props) ou rien (undefined)!

Typer children avec TypeScript

Option 1 : ReactNode

interface CardProps {

children: ReactNode;

}

Option 2 : PropsWithChildren (recommandé)

type CardProps = PropsWithChildren<{

title: string;

}>;

Ajoute automatiquement children Ă  votre interface

ReactNode = le type le plus large

type ReactNode =

| ReactElement // <div />, <Component />

| string // "texte"

| number // 42

| boolean // false (n'affiche rien)

| null // n'affiche rien

| undefined // n'affiche rien

| ReactNode[] // tableau de ReactNode

C'est pourquoi PropsWithChildren est pratique

Il ajoute children: ReactNode automatiquement Ă  votre type

Composition > Héritage

Le principe fondamental de React

En React, on ne fait JAMAIS d'héritage de composants

Pas de class Card extends BaseCard

Pourquoi pas d'héritage ?

L'héritage crée des couplages

Modifier la classe parent casse les enfants

Hiérarchie rigide

Impossible de combiner plusieurs comportements

// En React, on NE FAIT PAS ça :

class PrimaryButton extends Button { }

class DangerButton extends Button { }

La composition permet d'assembler des pièces

Comme des LEGO, pas comme un arbre généalogique

Composition en action

// Un seul composant Button, des variants par props

function Button({ variant, children }) {

const styles = {

primary: "bg-blue-500",

danger: "bg-red-500",

};

return <button className={styles[variant]}>{children}</button>;

}

Utilisation :

<Button variant="primary">OK</Button>

<Button variant="danger">Supprimer</Button>

Composants de Layout

La base d'une bonne architecture frontend

Card, Modal, Sidebar, Layout, Stack, Grid...

Tous utilisent children!

Demo : Composant Card

type CardProps = PropsWithChildren<{

title?: string;

}>;

function Card({ title, children }: CardProps) {

return (

<div className="border rounded-lg p-4 shadow">

{title && <h2>{title}</h2>}

{children}

</div>

);

}

Card en action

<Card title="Mon profil">

<img src="/avatar.jpg" />

<p>Développeur React</p>

<Button>Contacter</Button>

</Card>

Le contenu est totalement flexible

Card ne sait pas ce qu'il contient, il se contente de l'afficher

Demo : Composant Modal

function Modal({ isOpen, onClose, children }) {

if (!isOpen) return null;

return (

<div className="fixed inset-0 bg-black/50" onClick={onClose}>

<div className="bg-white p-6 rounded" onClick={e => e.stopPropagation()}>

{{children}}

</div>

</div>

);

}

Le Modal est générique : formulaire, confirmation, image, tout est possible!

Demo : Composant Layout

function Layout({ sidebar, children }) {

return (

<div className="flex">

<aside>{sidebar}</aside>

<main>{children}</main>

</div>

);

}

Utilisation avec plusieurs "slots" :

<Layout sidebar={<Sidebar />}>

<Dashboard />

</Layout>

Compound Components

Pattern avancé mais très puissant

Plusieurs composants qui travaillent ensemble

Partagent un état implicite

Pourquoi utiliser Context ?

❌ Sans Context (prop drilling)

<Tabs activeIndex={i}>

<Tabs.List activeIndex={i}>

<Tabs.Tab index={0} active={i===0}>

<Tabs.Tab index={1} active={i===1}>

</Tabs.List>

<Tabs.Panels activeIndex={i}>

<Tabs.Panel index={0} active={i===0}>

</Tabs.Panels>

</Tabs>

Props répétées, source d'erreurs!

âś… Avec Context

<Tabs>

<Tabs.List>

<Tabs.Tab>Onglet 1</Tabs.Tab>

<Tabs.Tab>Onglet 2</Tabs.Tab>

</Tabs.List>

<Tabs.Panels>...</Tabs.Panels>

</Tabs>

API clean, état partagé automatiquement!

Context = état implicite partagé entre composants imbriqués

Chaque sous-composant accède à l'état via useContext(), pas de props!

Exemple : Tabs (onglets)

// API déclarative et élégante

<Tabs>

<Tabs.List>

<Tabs.Tab>Profil</Tabs.Tab>

<Tabs.Tab>Paramètres</Tabs.Tab>

</Tabs.List>

<Tabs.Panels>

<Tabs.Panel>Contenu profil</Tabs.Panel>

<Tabs.Panel>Contenu params</Tabs.Panel>

</Tabs.Panels>

</Tabs>

Les composants Tabs.Tab et Tabs.Panel communiquent implicitement

Implémentation avec Context

const TabsContext = createContext();

function Tabs({ children }) {

const [activeIndex, setActiveIndex] = useState(0);

return (

<TabsContext.Provider value={{ activeIndex, setActiveIndex }}>

{children}

</TabsContext.Provider>

);

}

function Tab({ children, index }) {

const { activeIndex, setActiveIndex } = useContext(TabsContext);

return (

<button onClick={() => setActiveIndex(index)}>

{children}

</button>

);

}

Attacher les sous-composants

// Attacher les sous-composants au composant principal

Tabs.List = TabsList;

Tabs.Tab = Tab;

Tabs.Panels = TabsPanels;

Tabs.Panel = TabPanel;

Avantages :

  • API dĂ©clarative et lisible
  • État partagĂ© automatiquement
  • FlexibilitĂ© maximale pour l'utilisateur

Render Props

Une prop qui est une fonction

Le composant appelle la fonction pour render

Passe des données en paramètre

Pourquoi utiliser Render Props ?

🔄 Inversion de contrôle

Le composant gère la logique

L'utilisateur décide le render

// Mouse gère le tracking

// Vous décidez quoi afficher

♻️ Réutilisabilité maximale

Un composant = plusieurs rendus

<Mouse render={pos => <Cursor />} />

<Mouse render={pos => <Tooltip />} />

<Mouse render={pos => <Heatmap />} />

Cas d'usage typiques :

  • • Formulaires (validation, Ă©tat)
  • • Data fetching (loading, error)
  • • Animations (progress, state)
  • • ÉvĂ©nements (scroll, resize, mouse)

Exemple : Mouse position

function Mouse({ render }) {

const [position, setPosition] = useState({ x: 0, y: 0 });

return (

<div onMouseMove={e => setPosition({ x: e.clientX, y: e.clientY })}>

{render(position)}

</div>

);

}

Utilisation :

<Mouse render={({ x, y }) => (}

<h1>Position: {x}, {y}</h1>

)} />

children comme render prop

// children peut aussi ĂŞtre une fonction!

function Mouse({ children }) {

const [pos, setPos] = useState({ x: 0, y: 0 });

return (

<div onMouseMove={...}>

{children(pos)}

</div>

);

}

Utilisation :

<Mouse>

{({ x, y }) => <span>{x}, {y}</span>}

</Mouse>

Composants avec Slots

Plusieurs zones de contenu personnalisable

Une extension naturelle de children

header, footer, sidebar, actions...

Card avec slots multiples

type CardProps = {

header?: ReactNode;

footer?: ReactNode;

children: ReactNode;

};

function Card({ header, footer, children }: CardProps) {

return (

<div className="card">

{header && <header>{header}</header>}

<main>{children}</main>

{footer && <footer>{footer}</footer>}

</div>

);

}

Utilisation des slots

<Card

header={<h2>Mon titre</h2>}

footer={<Button>Valider</Button>}

>

<p>Contenu principal...</p>

</Card>

Chaque slot est optionnel et indépendant

L'utilisateur décide quoi mettre dans chaque zone

Pièges courants à éviter

1. Oublier de typer children

// Sans type, TypeScript se plaint

function Card({ children }) { // Erreur!

Utilisez PropsWithChildren ou ReactNode

2. Oublier de render children

function Card({ children }) {

return <div></div> // children disparaît!

}

Toujours inclure {children} dans le JSX

Pièges courants (suite)

3. Nesting excessif

// 10 niveaux de profondeur = illisible

<A><B><C><D>...</D></C></B></A>

Limiter Ă  3-4 niveaux maximum

4. Composants trop spécifiques

function UserProfileCardWithAvatarAndActions() { } // Non!

Créez des composants génériques et composez-les

Erreur vs Solution

L'erreur

function Card({ children }) {

return <div className="p-4"></div>;

}

// children n'est jamais affiché!

La solution

function Card({ children }) {

return <div className="p-4">

{children}

</div>;

}

Points clés à retenir

children est une prop spéciale de type ReactNode

Elle contient tout ce qu'on met entre les balises ouvrante et fermante

En React, on ne fait JAMAIS d'héritage

Composition uniquement - comme des LEGO

PropsWithChildren est le type utilitaire recommandé

Ajoute automatiquement children Ă  votre interface

Récapitulatif des patterns

children

Contenu entre balises

Le plus simple

Slots

Plusieurs props ReactNode

header, footer, etc.

Compound

Sous-composants liés

Tabs.List, Tabs.Tab

Render props

Prop = fonction

render={({ x }) => ...}

Exercices pratiques

1. Créer un composant Card

Avec title (optionnel) et children, typé avec PropsWithChildren

2. Créer un composant Modal

Avec isOpen, onClose et children - gérer le backdrop

3. Créer un Layout avec slots

header, sidebar, footer et children

4. Créer un compound component Accordion

Accordion.Item, Accordion.Title, Accordion.Content

Ă€ retenir!

children = le slot universel de React

Composition > Héritage (toujours!)

PropsWithChildren pour typer facilement

Toujours render {children} dans le JSX

Questions?