Skip to content

Composition Patterns

React’s answer to reuse is not inheritance — it is composition. Instead of extending a component, you pass content and behavior into it. The simplest form is the children prop: a component accepts arbitrary JSX and decides where to place it.

// A generic Card that doesn't know or care what's inside it.
function Card({ title, children }) {
return (
<section className="card">
<h3>{title}</h3>
<div className="card-body">{children}</div>
</section>
);
}
<Card title="Profile">
<Avatar user={user} />
<p>{user.bio}</p>
</Card>

You can pass JSX through any prop, not just children<Layout sidebar={<Nav />} main={<Feed />} /> is composition too. This is how you avoid deep prop-drilling and rigid inheritance trees.

When several components need to work together and share state, the compound component pattern exposes a family of pieces that coordinate through a shared (usually context-based) state — while the consumer arranges them freely.

// The consumer composes the pieces; Tabs shares state via context internally.
<Tabs defaultValue="a">
<TabList>
<Tab value="a">First</Tab>
<Tab value="b">Second</Tab>
</TabList>
<TabPanel value="a">Panel A</TabPanel>
<TabPanel value="b">Panel B</TabPanel>
</Tabs>

Internally, Tabs provides the active value + a setter via context; Tab and TabPanel read it with useContext. The consumer gets a clean, declarative API and full control over layout, without threading props through every piece. This is how libraries like Radix and Headless UI structure their components.

flowchart TB
  tabs["Tabs (provides value + setValue via context)"] --> list["TabList"]
  list --> tab["Tab (reads context, calls setValue)"]
  tabs --> panel["TabPanel (reads context, shows if active)"]
Compound components share state through context

Render props, and why hooks replaced most of them

Section titled “Render props, and why hooks replaced most of them”

A render prop is a prop whose value is a function that returns JSX — letting a component share logic while the caller controls rendering.

// Render prop: MouseTracker owns the logic, the caller renders.
<MouseTracker render={({ x, y }) => <p>Cursor at {x}, {y}</p>} />

Render props were the classic way to share stateful logic before hooks. Today, a custom hook does the same thing more directly — no wrapper component, no nesting:

// The modern equivalent: a custom hook.
function Component() {
const { x, y } = useMousePosition(); // logic shared via a hook
return <p>Cursor at {x}, {y}</p>; // caller renders normally
}

You will still see render props (some libraries use them, and children-as-a-function is one), but for sharing your own logic, reach for a custom hook first.

A form input is controlled when React state is its single source of truth (value + onChange), and uncontrolled when the DOM holds the value and you read it via a ref (or, in React 19, from the form’s FormData on submit).

// Controlled: state drives the input; you can validate/transform on every keystroke.
const [name, setName] = useState("");
<input value={name} onChange={(e) => setName(e.target.value)} />
// Uncontrolled: the DOM owns the value; read it when you need it.
const ref = useRef(null);
<input defaultValue="Ada" ref={ref} /> // ref.current.value on submit

Use controlled when you need to react to or constrain input as it changes; uncontrolled for simple forms where you only care about the final value (less state, less re-rendering).

What is React's primary mechanism for reuse?
How do compound components (like `<Tabs><Tab/></Tabs>`) usually share state?
What modern feature replaced most render-prop usage for sharing logic?
When is a controlled input the right choice?