skip to content

Props and Composition

Props flow down and composition is how React replaces inheritance. This group covers children as a slot plus the reuse patterns — render props, HOCs, compound components — that senior interviews use to test API design taste, not just syntax.

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

explore

questions

17

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

Why did custom hooks replace render props and higher-order components as React's default way to reuse stateful logic? Name the concrete problems with the older patterns.

level: middleimportance: must knowfreq 60%

basics

~20 s

Both older patterns reuse logic by adding components: HOCs stack into deep wrapper chains with silent prop collisions and hidden prop origins, and render props nest into callback pyramids. A hook is a plain function call whose result you name yourself, adding nothing to the tree.

open as a page

In React, what is the render-prop pattern — including the function-as-children form — and how does the callback get the data it renders?

level: juniorimportance: should knowfreq 50%

basics

~20 s

A render prop is a prop whose value is a function that returns JSX. The component holding the state calls that function during its own render and passes its data in as arguments, so the caller controls the markup while the component keeps the behaviour.

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 React, what is a compound component API such as `<Tabs><TabList><Tab/></TabList><TabPanel/></Tabs>`, and what do you gain and lose compared with a single `<Tabs items={[...]} />` that takes a config array?

level: middleimportance: should knowfreq 50%

basics

~20 s

A compound component splits one widget into a parent and related parts that share state implicitly through React context, so the consumer arranges the parts in JSX. It trades discoverability and enforceable structure for markup and layout freedom.

open as a page

In a React compound component such as `<Tabs><TabList><Tab id="a"/></TabList></Tabs>`, how does a `<Tab>` nested several levels deep learn which tab is selected, and what happens if someone renders `<Tab>` outside any `<Tabs>`?

level: middleimportance: should knowfreq 45%

basics

~20 s

The parent publishes the shared state on a React context and each part reads it with useContext, so nesting depth is irrelevant. A part rendered outside the parent silently gets the context default instead, so make the part's hook throw a named error.

open as a page

What is a higher-order component in React — a `withX(Component)` wrapper — and what commonly breaks when one is written naively?

level: middleimportance: should knowfreq 55%

basics

~20 s

A higher-order component is a function that takes a component and returns a new component wrapping it. Naive versions swallow props, appear unnamed in DevTools, lose the inner component's static properties, and remount the whole subtree if the wrapper is created during render.

open as a page

In React 19, a `withTooltip(Button)` wrapper is rendered with a `ref` so the caller can focus the underlying `<button>`. Does the ref reach it, and how did this differ in React 18?

level: middleimportance: should knowfreq 40%

basics

~20 s

In React 19 the ref reaches the inner component, because ref is an ordinary prop on function components, so a wrapper that spreads its props forwards it automatically. In React 18 and earlier the ref was dropped unless the wrapper was built with forwardRef.

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

How do you design a React `<Tabs>` compound component that supports both `<Tabs defaultValue="overview">` and `<Tabs value={tab} onValueChange={setTab}>`, and what do the parts see in each case?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Always keep internal state seeded from defaultValue, treat the widget as controlled whenever the value prop is not undefined, and publish the resolved value plus one select callback through context. The parts read only that, so they never know which mode is active.

open as a page

In a React 19 codebase where custom hooks are the default, when is a render prop still the right API for a reusable component, and what does a hook not give you?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A render prop still wins when the reusable unit must own rendering or a place in the tree: item renderers for virtualization, wrapper elements that measure or capture input, and headless components that supply state and props but not markup. Hooks run inside the caller and render nothing.

open as a page

In a React component library, what does attaching parts as static properties — `Tabs.List = TabList; Tabs.Panel = TabPanel;` — actually give you, and what does it not do?

level: juniorimportance: nice to knowfreq 30%

basics

~20 s

It is plain JavaScript property assignment, and JSX accepts a dotted tag, so <Tabs.Panel /> renders that component. It groups the API under one import and one name; it enforces nothing about where the parts may be used.

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

You maintain a shared React component library. How do you decide which widgets are worth exposing as compound components, and how do you contain the costs of an implicitly coupled API once you ship one?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Give a compound API to widgets whose arrangement genuinely varies across product surfaces, and keep a fixed-structure API where correctness or accessibility depends on that arrangement. Contain the cost with loud runtime guards, worked examples, and an opinionated preset built on the same parts.

open as a page

You lead a large React codebase where most screens are exported through chains of legacy `withX` higher-order components. How do you plan the move to hooks without a freeze-and-rewrite?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Move incrementally: implement each behaviour once as a hook, redefine the legacy wrapper as a thin shim over that hook so both call styles share one implementation, then convert call sites leaf-first while blocking new wrapper usage. Rank by debugging pain, and accept permanent residue.

open as a page