Component Composition and the Children Prop

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>
  );
}
Note: The children prop can be any valid React content: a single element, multiple elements, a string, a number, an array, or even undefined (when the component is self-closed: <Card />). You can provide a fallback for empty children: {children ?? <p>No content</p>}.
Tip: Prefer composition over prop drilling for layout and container components. Instead of adding boolean props like 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.
Warning: Avoid over-composing. Not every piece of JSX needs to be extracted into a component. A good heuristic: extract when the same JSX structure appears in two or more places, when a logical section grows beyond 30โ€“40 lines and can be named clearly, or when you want to add independent state. Do not create a component just to give a one-off, two-line block of JSX a name.

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

🧠 Test Yourself

You create function Section({ title }) { return <section><h2>{title}</h2></section>; }. You use it as <Section title="Posts"><PostList /></Section> but the PostList does not render. Why?