Composition is one of React’s most powerful patterns โ it lets you build flexible, reusable components by passing JSX content as data. The children prop is how React passes nested JSX into a component, making it possible to create wrapper components like Card, Modal, Layout, and Section that do not know in advance what content they will contain. Understanding when to use composition instead of conditional props is what separates rigid, hard-to-reuse components from flexible, composable ones.
The children Prop
// Wrapper component โ renders whatever is passed between its tags
function Card({ children, className = '' }) {
return (
<div className={`card ${className}`}>
{children}
</div>
);
}
// Using the Card wrapper โ PostCard content becomes Card's children
function PostCard({ post }) {
return (
<Card className="post-card">
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</Card>
);
}
// Same Card used for completely different content
function UserProfileCard({ user }) {
return (
<Card className="profile-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
</Card>
);
}
<Card />). You can provide a fallback for empty children: {children ?? <p>No content</p>}.showHeader and showFooter, pass the header and footer as named slot props: header={<h1>Title</h1>} and footer={<Footer />}. This keeps the container component generic and puts rendering decisions in the parent where they belong.Composition vs Prop-Based Customisation
// Prop-based โ rigid, every variation needs a new prop
function PostCard({ post, showTags, showAuthor, showViewCount, showDate }) {
return (
<article>
<h2>{post.title}</h2>
{showAuthor && <p>By {post.author.name}</p>}
{showTags && <TagList tags={post.tags} />}
{showViewCount && <span>{post.viewCount} views</span>}
{showDate && <time>{post.createdAt}</time>}
</article>
);
}
// Composition-based โ flexible, caller decides what to render
function PostCard({ post, header, footer, children }) {
return (
<article className="post-card">
{header}
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
{children}
{footer}
</article>
);
}
// Caller controls structure without modifying PostCard
<PostCard
post={post}
header={<span>โญ Featured</span>}
footer={<TagList tags={post.tags} />}
>
<AuthorBio author={post.author} />
</PostCard>
Layout Composition โ the Page Shell
function PageLayout({ children }) {
return (
<div className="page-layout">
<Header />
<main className="page-layout__content">
{children}
</main>
<Footer />
</div>
);
}
// Every page uses the layout
function HomePage() {
return (
<PageLayout>
<HeroBanner />
<RecentPosts />
</PageLayout>
);
}
function PostPage({ post }) {
return (
<PageLayout>
<article><h1>{post.title}</h1></article>
</PageLayout>
);
}
Common Mistakes
Mistake 1 โ Forgetting to render children inside a wrapper
โ Wrong โ children accepted but not rendered:
function Card({ children }) {
return <div className="card"></div>; // children silently discarded!
}
โ Correct โ always render the children prop:
function Card({ children }) {
return <div className="card">{children}</div>; // โ
}
Mistake 2 โ Deep prop drilling instead of composition
โ Wrong โ passing user through many layers just to reach UserMenu:
<Page user={user}>
<Sidebar user={user}>
<UserMenu user={user} />
</Sidebar>
</Page>
โ Better โ compose the fully formed UserMenu at the level that knows about the user:
<Page sidebar={<Sidebar menu={<UserMenu user={user} />} />} />
Mistake 3 โ Over-extracting single-use blocks into components
โ Wrong โ extracting a block used exactly once:
function PostCardAuthorLine({ author }) { // used in only one place
return <span>By {author.name}</span>;
}
โ Correct โ inline simple, one-off JSX; extract only when there is reuse or complexity.
Quick Reference
| Pattern | Code |
|---|---|
| Wrapper with children | function Card({ children }) { return <div>{children}</div>; } |
| Named slot prop | function Layout({ header, children, footer }) { ... } |
| Pass JSX as prop | <Layout header={<Nav />}>...</Layout> |
| Children fallback | {children ?? <p>No content</p>} |
| Check has children | React.Children.count(children) > 0 |