ShaharAmir
← Back to Blog
React2 min read

Render Props Pattern in React

Share component logic with a function as a child

S
Shahar Amir

Render Props Pattern in React

Hooks are great, but sometimes render props are cleaner. Here's when and how to use them.

The Pattern

Pass a function that returns JSX:

javascript
1234567
<DataFetcher url="/api/users">
{({ data, loading, error }) => {
if (loading) return <Spinner />;
if (error) return <Error message={error} />;
return <UserList users={data} />;
}}
</DataFetcher>

The component handles logic; you control rendering.

Building a Render Prop Component

javascript
12345678910111213141516171819
function Toggle({ children }) {
const [on, setOn] = useState(false);
return children({
on,
toggle: () => setOn(prev => !prev),
setOn,
});
}
// Usage
<Toggle>
{({ on, toggle }) => (
<div>
<p>The toggle is {on ? "on" : "off"}</p>
<button onClick={toggle}>Toggle</button>
</div>
)}
</Toggle>

Mouse Position Example

javascript
12345678910111213141516171819202122
function MouseTracker({ children }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener("mousemove", handleMove);
return () => window.removeEventListener("mousemove", handleMove);
}, []);
return children(position);
}
// Usage
<MouseTracker>
{({ x, y }) => (
<div style={{ transform: `translate(${x}px, ${y}px)` }}>
🎯
</div>
)}
</MouseTracker>

As a Prop Instead

javascript
1234
<DataFetcher
url="/api/users"
render={({ data }) => <UserList users={data} />}
/>

Same pattern, different syntax.

When Render Props Beat Hooks

Use render props when:

  • You need different UI for the same logic
  • Building component libraries
  • The logic affects what gets rendered

Use hooks when:

  • You just need the data
  • Logic is simple
  • No need for render flexibility

Real-World Example

javascript
1234567891011121314151617181920212223242526272829
function Disclosure({ defaultOpen = false, children }) {
const [open, setOpen] = useState(defaultOpen);
return children({
open,
toggle: () => setOpen(prev => !prev),
buttonProps: {
onClick: () => setOpen(prev => !prev),
"aria-expanded": open,
},
contentProps: {
hidden: !open,
},
});
}
// Usage
<Disclosure>
{({ buttonProps, contentProps, open }) => (
<div>
<button {...buttonProps}>
FAQ {open ? "▼" : "▶"}
</button>
<div {...contentProps}>
<p>Answer goes here...</p>
</div>
</div>
)}
</Disclosure>

The Benefit

You get logic from the component but keep full control over rendering. It's the ultimate separation of concerns.

#patterns#composition#reuse

Stay Updated 📬

Get the latest tips and tutorials delivered to your inbox. No spam, unsubscribe anytime.