Render props pattern in React
Render props: a component takes a function as a child (or prop) and calls it with internal state. `<Mouse>{({x, y}) => <p>{x}</p>}</Mouse>` — the consumer decides what to render. Pre-hooks pattern for sharing logic. Mostly replaced by custom hooks; still useful for inversion of control (forms, drag, headless UI).
The pattern
function Mouse({ children }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const handler = (e) => setPos({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handler);
return () => window.removeEventListener('mousemove', handler);
}, []);
return children(pos);
}
<Mouse>{({ x, y }) => <p>{x}, {y}</p>}</Mouse>children is a function; the parent passes state, the consumer renders.
Why it existed
Pre-hooks, sharing logic between components required HOCs or render props. Both:
- HOC:
withMouse(Component)wraps and injects. - Render prop:
<Mouse>{fn}</Mouse>exposes state via callback.
Why hooks mostly replaced it
const pos = useMouse();Cleaner; no wrapper components; no "render prop hell."
Where render props still shine
Headless components / inversion of control
<Combobox>
{({ input, list }) => (
<>
<CustomInput {...input.props} />
<CustomList {...list.props}>{list.options.map(...)}</CustomList>
</>
)}
</Combobox>The component owns state + ARIA wiring; consumer owns UI. Downshift, react-hook-form's <Controller>, Radix examples.
Form fields with complex layouts
<Field name="email">
{({ value, onChange, error }) => <CustomInput ... />}
</Field>Drag / gesture libs
<Draggable>
{({ x, y, dragging }) => <div style={{ transform: ... }} />}
</Draggable>Tradeoffs
| Render props | Hooks |
|---|---|
| Inversion of control via JSX | Inversion via function call |
| Visible in component tree (debugger sees it) | Invisible in tree |
| Can scope state to a JSX subtree | Hook state per component |
| 'Wrapper hell' if nested | Stack of useX(), readable |
Common mistakes
- Re-creating the render function inline per render → memoized child remounts.
- Render-prop inside another render-prop → indentation hell.
- Doing too much in the render-prop component when a hook + small primitive would do.
Modern recipe
For new code: prefer hooks for logic and primitives for UI. Render props for headless-component patterns where consumer control is the whole point.
Interview framing
"Render props: a component takes a function (often as children) and calls it with internal state — consumer decides what to render. Pre-hooks pattern for sharing logic; mostly replaced by custom hooks now. Still useful for headless components and inversion of control — Downshift, react-hook-form's Controller, drag libraries. New code defaults to hooks; reach for render props when the consumer needs to fully control the rendered output while you own the state + a11y wiring."
Follow-up questions
- •Compare render props vs custom hooks.
- •When have you used a render prop recently?
- •What's a HOC and how does it compare?
Common mistakes
- •Render-prop hell from nesting.
- •Re-creating render function inline.
Performance considerations
- •Each render creates a new render function — memoize or accept the re-render cost.
Edge cases
- •Render prop + React.memo'd parent — function reference changes per render.
- •Using both children-as-function and named children.
Real-world examples
- •Downshift, react-hook-form Controller, Formik FieldArray, framer-motion's render prop forms.