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.