Before hooks existed, Render Props was the standard pattern for sharing stateful logic across components: a component tracks state internally but delegates the actual rendering to a function passed in as a prop. This lesson covers how the pattern works and where it still earns its place today.
1Sharing Logic Before Hooks Existed
Before hooks, there was no direct way to extract stateful logic into a reusable function. Render Props filled that gap: a component tracks state internally but delegates rendering decisions entirely to a function passed in as a prop, letting the consumer decide what to render with that state.
2Building a MouseTracker
A MouseTracker component listens for mousemove events and tracks position in its own state, but instead of rendering that position itself, calls a function prop with the current position and renders whatever that function returns — separating the tracking logic from the presentation entirely.
3children as a Function
A common variant passes the render function as children instead of a named prop, taking advantage of JSX allowing any expression — including a function — to be nested between a component's tags. This reads almost like normal JSX composition, just with a function instead of an element.
4Why Hooks Replaced Most Render Props Usage
A custom hook achieves the same logic reuse with significantly less nesting — no wrapper component and no function-as-prop indirection, just a value destructured directly at the call site. This is why most pre-hooks render-prop library patterns have since been rewritten as custom hooks.
5Where Render Props Still Make Sense
Render props remain useful specifically when a component needs to delegate JSX structure, not just a data value, to its consumer — a List component accepting a renderItem prop lets the consumer fully customize each item's markup, something a hook alone can't express since hooks only ever return data, never JSX structure.
6Step-by-Step Breakdown
Sharing Logic Before Hooks Existed. Before React introduced hooks, there was no way to extract stateful logic into a reusable function the way useMouseTracker() does today. Render Props was the pattern that filled that gap: a component tracks state internally but lets the CONSUMER decide what to render with it, via a function passed as a prop.
Building a MouseTracker. A MouseTracker component listens for mousemove events and tracks { x, y } in its own state — but instead of rendering that position itself, it calls a function prop, passing the position as an argument, and renders WHATEVER that function returns.
In <MouseTracker render={({ x, y }) => <p>{x}, {y}</p>} />, what decides the actual JSX output for the mouse position?
- →The function passed by the consumer as the render prop
- →Fixed JSX hardcoded inside MouseTracker itself
children as a Function. A common variant passes the function as children instead of a named render prop, taking advantage of JSX's ability to nest any expression between tags — including a function. <MouseTracker>{(pos) => <p>...</p>}</MouseTracker> reads almost like a normal composition, just with a function instead of an element.
Why Hooks Replaced Most Render Props Usage. A custom hook like useMousePosition() achieves the same logic reuse with far less nesting — no wrapper component, no function-as-prop indirection, just a value you destructure directly. This is why most render-prop patterns from pre-hooks React libraries have been rewritten as hooks.
Why have custom hooks largely replaced render props for sharing stateful logic?
- →Hooks provide the same reuse without an extra wrapper component or nesting
- →Render props were removed from React entirely
Where Render Props Still Make Sense. Render props haven't disappeared entirely — they're still useful when a component needs to control JSX STRUCTURE, not just expose a data value. A <List renderItem={(item) => <Card {...item} />} /> lets the consumer fully customize each item's markup, something a hook alone can't express, since a hook only returns data.
Mastery Achieved. You now understand render props: sharing logic by letting a component call a function prop instead of rendering fixed JSX itself, the children-as-a-function variant, why custom hooks replaced most of its uses for pure data sharing, and why it still earns its place when consumer-controlled JSX structure is the actual goal. Next, you'll learn Higher Order Components.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Render Props Let Consumers Fix Accessibility Issues in Rendered Output
Because the consumer controls the actual JSX returned from a render prop function, they can ensure correct semantic markup, ARIA attributes, or heading levels for their specific context, rather than being locked into a fixed structure the base component dictates.
SEO Implications
- 1
Render Props Have No Direct SEO Effect
This is a component composition pattern affecting how logic and rendering are separated in client code, with no direct bearing on server-rendered HTML content by itself.
Best Practices
Reach for a Custom Hook First for Pure Data Sharing
If a component's only job is exposing a value (like mouse position or online status) with no opinion about how it's rendered, a custom hook is almost always simpler than a render-prop wrapper component.
Use Render Props When the Consumer Must Control JSX Structure
Reserve render props for cases where a component needs to hand off actual markup decisions, like a list's per-item rendering, since that's something a hook's return value alone can't express.
Frequent Bugs
A render-prop function is recreated as a new inline arrow function on every parent render, causing the wrapped component to re-render unnecessarily.
If the wrapped component is expensive and wrapped in React.memo, memoize the render function itself with useCallback in the parent, so its reference stays stable across renders where its own dependencies haven't changed.
Deeply nested render props ('render prop hell') make a component tree hard to read.
For logic that's purely about sharing data, not JSX structure, replace the render-prop wrapper with an equivalent custom hook, which eliminates the wrapping nesting entirely.
Real-World Examples
A Customizable List with renderItem
A reusable List component needs to support completely different item layouts across different pages — sometimes a compact row, sometimes a full card. Accepting a renderItem prop, called once per item with that item's data, lets each consuming page define its own markup while List handles iteration, keys, and empty-state logic once.
function List({ items, renderItem, emptyMessage }) {
if (items.length === 0) return <p>{emptyMessage}</p>;
return <ul>{items.map(item => <li key={item.id}>{renderItem(item)}</li>)}</ul>;
}
<List items={products} renderItem={(p) => <ProductCard {...p} />} emptyMessage="No products" />