skip to content

Children and Composition Patterns

React's official answer to 'inheritance or composition?' is children and slot props. Knowing this also gives you the first cure for prop drilling — pass rendered content down instead of threading data through every layer; context and state libraries handle the rest.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

6

In React, what does a component's `children` prop contain, how does JSX populate it, and what shapes can its value take at runtime?

level: juniorimportance: must knowfreq 78%

answer

  1. nested JSX becomes a prop
  2. same props object as the others
  3. shape depends on how many
  4. one child is not an array
  5. undefined when nothing nested

basics

~20 s

children is an ordinary React prop that holds whatever JSX you nest between a component's opening and closing tags. Its value may be a string, a single element, an array of nodes, or undefined when nothing is nested.

solid answer

~40 s

`children` is not a special language feature — it is a normal prop with a reserved name. JSX takes everything nested between the opening and closing tags of `<Card>…</Card>` and puts it into the props object under the key `children`, alongside `title` or any other attribute. The value's shape depends on what you nested: one text node gives you a string, one element gives you that element object, several nested nodes give you an array, and `<Card />` with nothing inside leaves `children` as `undefined`. Because the shape is unstable, the component should render it (`{children}`) rather than index into it. Accepting `children` is what makes a component a container: `Card`, `Modal` and `Layout` can wrap arbitrary content without knowing anything about it, which is React's replacement for extending a base component.

code

jsx · 19 lines
jsx
function Card({ title, children }) {
  return (
    <section className="card">
      <h2>{title}</h2>
      <div>{children}</div>
    </section>
  );
}

export default function Demo() {
  return (
    <>
      <Card title="String">Just text</Card>
      <Card title="Element"><p>One node</p></Card>
      <Card title="Array"><p>First</p><p>Second</p></Card>
      <Card title="Undefined" />
    </>
  );
}

go deeper

for a junior

Be able to write a wrapper component that renders {children} and to say plainly that nested JSX arrives as an ordinary prop. Know that a single child is not an array.

for a middle

Explain how JSX turns nested content into the children key of the props object, list the shapes the value can take, and say why elements are inert descriptions until React renders them.

for a senior

Show the judgment: argue for a container that accepts arbitrary children over one that grows a configuration prop per use case, and point at the maintenance cost of components that inspect what was nested.

for a principal

Own the API-design angle — accepting children is a stability commitment, because a container that never inspects its content keeps working for consumers you have not met. Weigh that openness against the constraints a stricter, typed child contract buys you.

## children is an ordinary prop React components receive exactly one argument: a props object. JSX attributes become keys in that object, and JSX has one extra rule — whatever you nest between the opening and closing tags becomes the prop named `children`. So this JSX: ```jsx <Card title="Stats"> <Chart /> </Card> ``` produces a props object shaped roughly like `{ title: "Stats", children: <Chart /> }`. `children` sits in the same object as `title`; nothing about it is magic. You may even pass it as an explicit attribute (`<Card children={<Chart />} />`), though nesting is the convention because it reads like markup. The component decides where that content lands, by writing `{children}` somewhere in its own JSX: ```jsx function Card({ title, children }) { return ( <section className="card"> <h2>{title}</h2> <div className="card-body">{children}</div> </section> ); } ``` If a component never renders `{children}`, nested content simply does not appear — there is no implicit slot. ## The shapes the value can take This is where beginners get burned, because `children` is not consistently an array: - `<Card>Hello</Card>` → the string `"Hello"`. - `<Card>{42}</Card>` → the number `42`. - `<Card><p /></Card>` → a single React element object. - `<Card><p /><p /></Card>` → an array of two elements. - `<Card />` → `undefined`. - `<Card>{items.map(i => <Row key={i.id} />)}</Card>` → an array (and if you mix that with other nodes, an array containing a nested array). So `props.children.map(...)` throws a `TypeError` for the single-child case, and `props.children.length` silently means "number of characters" when the child is a string. Treat the value as opaque: render it, pass it along, or use React's `Children` helpers if you truly must walk it. ## What actually renders Whatever ends up in `children` follows the normal rules for renderable nodes. Elements, strings, numbers and arrays render. `null`, `undefined` and booleans render nothing at all — which is why `{isOpen && <Dialog />}` is safe to nest. This means a container can receive a child that produces no output, and it cannot tell the difference from the outside. ## Elements are descriptions, not DOM A React element you receive as `children` is an immutable plain object describing what to render — a type plus props. It is not a DOM node and not a rendered component. You cannot read its text content, measure it, or mutate its props. Crucially, the child component's function body has **not** run yet: creating `<Chart />` only builds the description; React calls `Chart` when it renders that element in the tree. That is why passing elements around is cheap. ## Why containers matter: containment over configuration A component that renders `{children}` is a *container*: it owns the frame and the caller owns the contents. Compare two designs for a modal: ```jsx // configuration: the modal must anticipate every body it will ever show <Modal title="Delete?" bodyText="..." showIcon iconName="warn" listItems={items} /> // containment: the modal owns the frame, the caller owns the content <Modal title="Delete?"> <WarningIcon /> <p>This cannot be undone.</p> </Modal> ``` The configuration version grows a new prop every time a new use case appears, and every prop is a branch inside the modal. The containment version never changes. This is React's answer to "how do I extend a component?" — you do not subclass it, you nest content inside it. A component that takes `children` and does not inspect it works with content that did not exist when it was written. ## Practical rules - Render `{children}`; do not index, slice, or count it without the `Children` helpers. - If your container needs several regions, do not try to split `children` by position — give each region its own prop and keep `children` for the primary one. - Do not require a specific child type unless you are deliberately building a stricter API; a container that accepts any node is the one that survives refactors. - Remember the shape rule when writing tests or guards: `children == null` is the reliable "nothing was passed" check, not `children.length === 0`.

  • If a component receives `children` but never writes `{children}` in its JSX, what does the user see?
    Nothing from the nested content. There is no implicit slot — React passes the value in props and stops there. The elements are created (they are cheap description objects) but never rendered, so the child components' function bodies never run and no DOM appears. Forgetting `{children}` in a new wrapper component is one of the most common "my content vanished" bugs.
  • Does writing `<ExpensiveTree />` as a child mean that component runs even if the parent hides it?
    No. `<ExpensiveTree />` evaluates to a small object describing what to render; React only calls the `ExpensiveTree` function when that element is actually placed in the rendered tree. If the container returns `null` or omits `{children}`, the child never executes. Creating elements you might not render is cheap by design.
  • How do you check whether a caller passed any content at all?
    Compare against null: `if (children == null) return null;` covers both `undefined` (nothing nested) and an explicitly passed `null`. Avoid `children.length`, which is character count for a string child and throws for a single element. If you need a real node count across all shapes, `Children.count(children)` normalizes it.

saying these in an interview costs you the question

  • Says props.children is always an array
  • Calls children.length to count nested nodes
  • Thinks children is a DOM node you can read
  • Believes children must be declared or registered somehow
  • Assumes nested elements render even without {children}

context

open as a page

Inside a React component, why can calling `props.children.map(...)` throw or silently skip nodes, and what do the `Children` helpers exported by React do differently?

level: middleimportance: should knowfreq 36%

basics

~20 s

The children prop is not reliably an array: a single nested node is passed through as itself, so array methods are undefined on it. React's Children helpers normalize every shape, but they treat a fragment as one node and never look inside it.

open as a page

React has no mechanism for one component to extend another the way a subclass extends a base class. Given a generic `Dialog` component, how do you build a `ConfirmDialog` that is always the confirm variant, and what is the general rule React expects you to follow?

level: middleimportance: should knowfreq 52%

basics

~20 s

You write ConfirmDialog as a normal component that renders Dialog with preset props and its own nested content. React reuses code by composition only: containment passes content through children, and specialization wraps a general component in a more specific one.

open as a page

You are designing a React `PageLayout` component with distinct header, sidebar and main regions that callers fill in. Why give each region its own prop holding JSX instead of splitting `props.children` by position or by child type?

level: middleimportance: should knowfreq 46%

basics

~20 s

Named slot props make each region an explicit, order-independent, type-checkable part of the API. Splitting children by position or by inspecting child types is fragile: any wrapper, fragment or conditional around a region silently breaks the layout with no error.

open as a page

In a React app a `user` object is passed from `App` through `Layout` and `Sidebar` down to `Avatar`, and the two middle components never read it. How does restructuring those components around `children` remove the drilling, and where does the technique stop helping?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Have Layout and Sidebar accept children instead of the data, and let App build the finished element itself. The prop then crosses zero intermediate components. It stops helping once the consumer's position is decided deep inside — inside lists, routes or recursive trees the middle layer renders.

open as a page

A design-system `RadioGroup` gives its options a shared `name` and `checked` value by walking `React.Children.map` and calling `cloneElement` on each child. What breaks as real consumers adopt it, and what would you build instead?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Injection by cloneElement only reaches direct children, so any wrapper, fragment or conditional silently stops it and options lose their name. It also overrides caller-supplied props invisibly and is untyped, so a data-driven prop or an explicit shared-value API is the sturdier design.

open as a page