Composition over Configuration
Using children and slots instead of growing a component's props forever.
What you'll learn
- How a component ends up with a dozen boolean props
- Composing with
childreninstead - Passing elements as props for named slots
- Using composition to avoid prop drilling
How components go wrong
A component starts simple, then every new requirement adds a prop. Before long the signature is a list of switches, and the body is a thicket of conditionals:
<Dialog
title="Delete file"
showIcon
iconType="warning"
showFooter
primaryLabel="Delete"
secondaryLabel="Cancel"
hideSecondary={false}
danger
/>
Every caller must learn the switches; every new case adds another. Worse, the
props are not independent — hideSecondary only matters if showFooter is
true — so most combinations are meaningless, and nothing stops you writing one.
The alternative is to let the caller pass content instead of instructions.
children
The plainest form: whatever goes between the tags becomes children. The
component controls layout; the caller controls content.
Alert has one prop and no conditionals, yet renders two quite different
things. It cannot be configured wrongly, because there is nothing to configure.
Slots: elements as props
children gives you one hole. When a component has several distinct regions,
give each its own prop — a prop can hold an element just as easily as a string:
The footer is optional without a showFooter prop: pass one or do not. The
absence of the element is the switch, and the impossible states disappear.
Specialisation
When a configuration recurs, do not add a flag — wrap the general component in a specific one. The specific component becomes the readable name for that case:
Note ...props — spreading the rest of the props through means the wrapper does
not have to re-declare onClick, disabled, type and the rest. That is the
rest and spread from the refresher earning its
keep.
Composition beats prop drilling
Passing a prop through layers that do not use it is often avoidable without context. If a parent already has the data, it can build the element and pass it down as content — the middle layer never sees it:
Layout takes no user prop, so it never has to change when the badge does.
Reach for context when composition cannot reach —
not as the first answer to drilling.
When configuration is right
Composition is not always the answer. A prop is better when the value really is
data rather than content: <Input type="email" />, <Avatar size={32} />,
<List items={rows} />. The smell is not props in general — it is booleans
that turn parts of the render on and off, especially several that interact.
Common mistakes
- A boolean per variation. Three flags make eight states you must think about; most are nonsense.
- Rebuilding
children. If a wrapper only ever renders one thing, it did not need to be configurable. - Context for everything. Try passing content down first.
Exercise
Notice has grown three booleans that interact. Rewrite it to take children
and an optional action slot, then update both call sites — the flags should
disappear entirely.