children, compound components & render props
Utilisez les flèches, cliquez ou glissez pour naviguer
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
Rappel : les props passent des données
Mais comment passer du JSX entier Ă un composant ?
La prop children
Le slot universel de React
Composition vs Héritage
Pourquoi en React, on ne fait JAMAIS d'héritage
Demo live : composants de layout
Card, Modal, Sidebar réutilisables
Compound components & Render props
Patterns avancés pour des composants flexibles
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 ?
Le slot universel de React
children contient tout ce qu'on met entre les balises
ouvrante et fermante du composant
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>
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)!
Option 1 : ReactNode
interface CardProps {
children: ReactNode;
}
Option 2 : PropsWithChildren (recommandé)
type CardProps = PropsWithChildren<{
title: string;
}>;
Ajoute automatiquement children Ă votre interface
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
Le principe fondamental de React
En React, on ne fait JAMAIS d'héritage de composants
Pas de class Card extends BaseCard
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
// 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>
La base d'une bonne architecture frontend
Card, Modal, Sidebar, Layout, Stack, Grid...
Tous utilisent children!
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 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
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!
function Layout({ sidebar, children }) {
return (
<div className="flex">
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}
Utilisation avec plusieurs "slots" :
<Layout sidebar={<Sidebar />}>
<Dashboard />
</Layout>
Pattern avancé mais très puissant
Plusieurs composants qui travaillent ensemble
Partagent un état implicite
❌ 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!
// 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
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 au composant principal
Tabs.List = TabsList;
Tabs.Tab = Tab;
Tabs.Panels = TabsPanels;
Tabs.Panel = TabPanel;
Avantages :
Une prop qui est une fonction
Le composant appelle la fonction pour render
Passe des données en paramètre
🔄 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 :
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 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>
Plusieurs zones de contenu personnalisable
Une extension naturelle de children
header, footer, sidebar, actions...
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>
);
}
<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
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
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
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>;
}
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
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 }) => ...}
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
children = le slot universel de React
Composition > Héritage (toujours!)
PropsWithChildren pour typer facilement
Toujours render {children} dans le JSX
Questions?