the idea: a prop that is a function
A normal child is static JSX: the parent decides what to draw.
A render prop flips that — the component owns the state and
calls props.render(state) (or props.children(state)) to let the
consumer decide what to draw with that state. The component is in charge of what data
exists; the consumer is in charge of how to show it. This is
inversion of control — the same trick React itself uses for
Array.map(item => <li/>) and <Context.Consumer>{value => ...}</Context.Consumer>.
| form | shape | who uses it |
|---|---|---|
render prop |
<X render={state => <p/>}/> |
the classic form from the React docs (React < 16.3 MouseTracker example) |
children as function |
<X>{state => <p/>}</X> |
react-spring, react-motion, downshift, Framer Motion (idiomatic since 2017) |
| multi-arg render | render={(state, helpers) => ...} |
downshift {({getItemProps, highlightedIndex}) => ...} |
| prop injection | <List row={item => <Card item={item}/>}/> |
virtualized lists, data tables — consumer supplies the row renderer |
1 · the components you write (edit me)
2 · Babel compiles JSX → element tree
<RenderPropsDemo/> becomes
React.createElement(RenderPropsDemo). Inside, the consumer's function is
passed as a prop and called by the component — Babel just turns the JSX sugar into nested
createElement calls. The render-prop call site
(props.render(pos)) is plain JS, untouched.
// (hit "compile & render" to see Babel's output)
3 · live React (inversion of control, proven)
MouseTracker calls your render function with mouse coordinates.
Counter calls your children function with the count and helpers.
Click + / − /
Reset — the gold-check does
this automatically and asserts the counter walks 0 → 1 → 2 → 1 → 0.
count: — · mouse: — · gold: 0 → + → 1 → + → 2 → − → 1 → Reset → 0
intent → pattern
| intent | pattern | why |
|---|---|---|
| expose internal state to consumer | <X render={s => <p/>}/> |
component owns state, consumer owns rendering — full inversion of control |
| idiomatic / "library style" | <X>{s => <p/>}</X> |
children-as-function reads naturally; used by react-spring, downshift |
| pass state AND actions | {(state, helpers) => ...} |
downshift ships {getItemProps, highlightedIndex} this way |
| inject a row renderer | <List row={item => <Card item={item}/>}/> |
the list manages keys/virtualization; you supply the visual |
| prefer hooks instead | function useMouse() { ... } return useMouse() |
when you don't need to control rendering — hooks replaced ~80% of render props |
| perf: function identity | wrap consumer's render fn in useCallback |
inline {() => ...} breaks React.memo / PureComponent |