Render Props — inversion of control

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Builds on frontend/react: components & props (props can be functions too — this is the foundation).

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>.

formshapewho 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

intentpatternwhy
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