Render Props Pattern: Pass a Renderer via a Named Prop
Similar to function children, but explicit—pass a render function to customize output.
Every screen that needed a list had its own items.map(...) pasted in. I wanted one reusable List where the list logic lives once and each consumer decides what an item looks like.
You could do this with function children, but that API isn’t self-documenting; nothing at the call site tells you the child is a function. A named render prop like renderItem makes the contract explicit.
The same map, copy-pasted
Mapping the items inline, in every component that needs a list:
function Fruits() {
const items = ["Apple", "Banana", "Cherry"];
return (
<ul>
{items.map((item, index) => <li key={index}>{item}</li>)}
</ul>
);
}
function App() {
return (
<div>
<h1>Render Props Pattern</h1>
<Fruits />
</div>
);
}
export default App;
Fine the first time. The duplication starts the moment a second place needs a list.
Give List a renderItem
List accepts a renderItem prop and calls it for each item:
function List({ items, renderItem }) {
return (
<ul>
{items.map((item, index) => renderItem(item, index))}
</ul>
);
}
function App() {
const items = ["Apple", "Banana", "Cherry"];
return (
<div>
<h1>Render Props Pattern</h1>
<h2>List</h2>
<List items={items} renderItem={(item, index) => <li key={index}>{item}</li>} />
</div>
);
}
export default App;
List maps over items and calls renderItem(item, index); the parent’s function returns a keyed <li>. All item rendering stays outside List, so the same component works with different renderers wherever you need it.
Name the prop after its job (renderRow, renderEmpty, renderHeader). Keep the function pure, no side effects inside it. And once you’re passing several render functions to one component, that’s when I’d move to compound components instead.