Chapter 14 of 20 · React for Beginners — Complete Free Tutorial
Component Composition and Children
There is a moment in every growing React for Beginners project when a wrapper component multiplies its props: Card grows title, subtitle, image, footer, actions… until it knows everything about its contents. The escape hatch has always been there — props.children. This chapter covers the children prop, slot-style composition, and why combining small components beats configuring one big one.
The children prop
Anything you nest between a component's tags arrives automatically as props.children. The wrapper does not need to know what it is wrapping:
function Card({ children }) {
return <div className="card">{children}</div>;
}
function App() {
return (
<Card>
<h2>Monthly report</h2>
<p>Revenue grew 12%</p>
<button>Export</button>
</Card>
);
}
Expected output: a card containing a heading, paragraph and button — passed in as markup, not as props. The Card never changes when its contents do; it owns presentation only.
Slots: named pieces of content
When a wrapper needs structure with several holes, pass elements through named props instead of inventing data props. Each prop holds JSX, so the parent controls what goes in each slot:
function Layout({ header, sidebar, children }) {
return (
<div className="layout">
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}
// Usage
<Layout
header={<NavBar />}
sidebar={<Filters />}
>
<ResultsTable />
</Layout>
Expected output: a three-region page where each region is supplied as an element by the caller — the layout constrains structure, the caller supplies content.
Composition vs configuration
The choice between "many props" and "children/slots" is really a choice between two design styles:
| Configuration (props) | Composition (children/slots) |
|---|---|
Parent passes data: title, items, tone | Parent passes elements: {children}, header |
| Wrapper decides how to render each prop | Caller decides what the content is |
Great for simple, repeated variants (a button's variant) | Great for structure — layouts, cards, modals, lists of arbitrary markup |
| Grows prop count forever | Wrapper stays small and stable |
A practical rule: if a prop expects markup, it should probably accept an element (or be a child). If it expects data or a choice — colour, size, count — a plain prop is right.
Extracting components as composition
Composition is also how you fight oversized JSX. When a return value grows past a screenful, split it — each piece becomes testable and reusable:
function ProfilePage({ user }) {
return (
<Page>
<ProfileHeader user={user} />
<ProfileStats stats={user.stats} />
<ProfileFeed posts={user.posts} />
</Page>
);
}
Expected output: the same rendered page, now built from four readable components — each with a single job. The parent reads like a table of contents, and you can swap ProfileFeed for something else without touching the header.
Controlled visibility: the modal pattern
Composition shines for one-off UI: a Modal that renders whatever you give it, controlled by the parent's state. The wrapper handles positioning and the overlay; the parent decides the content and when it opens:
const [open, setOpen] = useState(false);
<button onClick={() => setOpen(true)}>Delete</button>
<Modal open={open} onClose={() => setOpen(false)}>
<h2>Are you sure?</h2>
<button onClick={confirmDelete}>Yes, delete</button>
</Modal>
Expected output: the modal's chrome is written once; each usage supplies its own body. Note that children may be a single element or an array — React normalises both, so callers can nest anything.
Display names and debugging
Composed trees show up in the React DevTools and error messages. Name your components clearly (a wrapper that only adds layout can be Panel, not Wrapper2) so a stack trace points at the right file. For truly generic helpers, setting Component.displayName helps tools label anonymous functions.
Common mistakes
- Passing children and a conflicting prop —
<Card title="A">…</Card>when Card renders both is confusing; pick one convention. - Configuring instead of composing — a wrapper with eight optional element props should accept children or slots instead.
- Cloning children to inject props —
cloneElementon children is brittle; pass an explicit prop or a render function instead. - Huge anonymous JSX — refusing to extract components until nobody can read the file; split at the first screenful.
- Wrapping for no reason — a div-per-div "component" adds files without adding meaning; compose only where structure or reuse is real.
Practice checklist
- Build a
Cardthat acceptschildrenand render three different bodies through it. - Create a
Layoutwith header/sidebar/main slots and fill each with a real component. - Refactor one long return value into three extracted components.
- Write a
Modaldriven by parent state with a close callback. - Audit a prop that expects markup and convert it to an element slot.
Composition keeps structure clean — the next chapter tackles data that the whole tree needs: Managing Global State with Context.
Frequently Asked Questions
React is a JavaScript library for building user interfaces from reusable components. It manages how your UI updates when data changes, so you describe what the screen should look like and React performs the efficient DOM updates. It is one of the most in-demand frontend skills and powers products from Netflix to Instagram, which makes it an excellent next step after core JavaScript.
You should be comfortable with functions, arrays and objects (map, filter, spread), destructuring, modules and basic async/await. React itself is written in plain JavaScript — JSX is a syntax layer on top. If any of those feel shaky, revisit the JavaScript fundamentals first and this series will go much faster.
React is a library focused only on the UI layer — you choose your own router, data layer and tooling. Angular is a full framework that ships routing, forms, HTTP client and testing conventions as one opinionated package. React's flexibility and huge ecosystem make it the common choice for teams that want to compose their own stack.
A component is a reusable function that returns UI. It receives input through props, keeps private data in state, and can render other components. Pages are composed by nesting small components (navbar, card list, footer) instead of one giant file, which keeps code testable and easy to change.
JSX is a syntax extension that looks like HTML but compiles to React's create() calls — it is a description of UI, not markup the browser parses. Small differences matter: class becomes className, style takes an object, and you embed JavaScript expressions inside curly braces. Once compiled, React turns that description into real DOM nodes.
Props are inputs passed from a parent — they are read-only inside the child, like function arguments. State is private, mutable data owned by a component; updating it triggers a re-render. The rule of thumb: values that come from outside the component are props; values that change over the component's lifetime are state.
Rendering means calling your component function to produce a description of the UI for the current state and props. It does not always mean DOM work — React compares the new description with the previous one (reconciliation) and applies only the differences to the real DOM. This is why updating state 100 times does not mean 100 full page rebuilds.
An effect runs after the component has committed to the screen — after render, not during it. You list dependencies in an array, and React re-runs the effect only when one of them changes. If the effect returns a cleanup function, React runs it before the next re-run and on unmount, which is where you cancel timers, abort fetches and remove listeners.
Use Vite. Create React App is effectively in maintenance mode, while Vite starts instantly (native ES modules) and builds fast, and it is the default choice for new React projects. This series uses Vite for every example — you only need Node.js installed to follow along.
React apps compile to static files (HTML, CSS and JavaScript), so any static host works: Netlify, Vercel, GitHub Pages or Cloudflare Pages. Run the production build, upload the dist folder, and configure a single-page-application fallback so unknown paths load index.html — routing chapters explain why that fallback is required.