The Render Props Pattern Is Not Dead — Here's Where It Still Wins

Hooks replaced most render props use cases in 2019. Most — not all. There are three scenarios where render props still outperform hooks and compound components. Here's what they are and how to implement them cleanly.
When React hooks shipped in 2019, the community declared render props dead. The pattern that had powered libraries like Formik, React Motion, and Downshift was suddenly unnecessary. You could extract any stateful logic into a hook and call it directly in your component. Cleaner. Simpler. No wrapper components.
That conclusion was mostly right. For most of what render props were doing — sharing stateful logic — hooks are better.
But "mostly right" is doing work in that sentence. There are cases where hooks can't replace render props, and forcing hooks into those cases produces code that's more complex, not less.
The question worth asking is not "render props or hooks?" It's "what problem am I actually solving?" The answer determines the tool.
What render props actually are
A render prop is a prop whose value is a function that returns JSX. The component calls that function and renders whatever it returns.
The component owns the logic. The consumer owns the rendering. That separation is the pattern's essential feature — and the one that hooks can't always replicate.
Why hooks replaced most render props
Before hooks, sharing stateful logic required either higher-order components or render props. Both introduced wrapper components. Deep component trees. The infamous "wrapper hell" that made React DevTools debugging painful.
Hooks collapsed this entirely:
For sharing logic, hooks win. No wrapper component. No nesting. The state lives wherever you call the hook.
Where render props still win
Scenario 1: the consumer controls rendering structure
This is the case hooks genuinely can't handle. When a component needs to provide logic but the consumer needs to control not just what renders, but where and how it renders in the DOM structure.
A virtual list is the clearest example. The list component handles the complex work — measuring item heights, calculating visible range, managing scroll position. But each row can be anything — a card, a table row, a grid cell, a custom layout. The list doesn't know and shouldn't know.
The consumer renders whatever they need inside each row — and gets the positioning styles passed in so they don't have to calculate anything:
Try implementing this with hooks. A useVirtualList hook would return the visible items and the scroll handler — but it can't tell the consumer how to position each item in its specific DOM structure. The render prop passes the style as an argument, letting the consumer apply it wherever makes sense for their layout.
Scenario 2: injecting logic into a specific DOM position
Sometimes you need a component to render something at an exact position in the consumer's JSX — not as a sibling, not wrapped, but injected into the middle of existing markup.
A DataTable with a render prop for custom cell content:
The cell render prop lives inside a <td> that the DataTable owns. There's no way to express "render this inside the cell for this column" with a hook — hooks don't return DOM structure, they return values.
Usage:
Three different cell types — a badge, a formatted number, a button — all living in the right DOM position because the render prop puts them there. A hook couldn't do this without the consumer building the entire table structure themselves.
Scenario 3: the hybrid pattern
The most useful evolution of render props is the hybrid: a hook for the logic, a render prop for the parts that need custom layout.
Usage with two completely different item layouts — same logic, different rendering:
Same keyboard navigation, same filtering, same state management — two completely different visual presentations. The hook owns all the logic. The render prop owns all the rendering. Neither compromises on its responsibility.
This hybrid is the state of the art for UI libraries like Downshift and Headless UI. It's why they're so flexible — the logic is complete and the rendering is entirely open.
The API design question
When building a component that might use render props, one naming decision matters: render prop vs children as a function.
Both work. children as a function reads more naturally for content-level rendering — the thing inside the component is its content. A named prop like renderItem or renderCell reads more naturally for slot-level customisation — you're customising a specific part, not the whole content.
The convention in modern libraries: children as a function for the primary content, named render props for specific slots.
The honest summary
Render props are not the default. Hooks are simpler and should be your first choice for sharing logic.
Reach for render props when:
- The consumer needs to control rendering structure at a specific DOM position
- A virtualised or windowed list needs to render arbitrary item shapes
- You want a hybrid: a hook for logic + a render prop for flexible rendering
In those cases, the render prop isn't a workaround — it's the right tool.
Up next: How to manage global state without a library — the useSyncExternalStore pattern for when you don't want React Context or Zustand.
Related: Compound components — the design pattern that changed how I build UIs — the context-powered pattern that often pairs with render props for maximum flexibility.
Alex Chen is a senior frontend engineer who writes about React patterns, JavaScript internals, and the decisions that separate maintainable codebases from ones that fight back. Opinionated by design.
Photo by Alexander Travkin on Unsplash

