Render Props
The render props pattern shares logic between components by passing a function as a prop. The function receives data and returns JSX.
Basic example
tsx
interface MousePosition { x: number; y: number; }
function MouseTracker({ render }: { render: (pos: MousePosition) => React.ReactNode }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={(e) => setPos({ x: e.clientX, y: e.clientY })}>
{render(pos)}
</div>
);
}
// usage - full control over what gets rendered
<MouseTracker render={({ x, y }) => (
<p>Mouse is at {x}, {y}</p>
)} />children as a function (most common variant)
tsx
function Toggle({ children }: { children: (on: boolean, toggle: () => void) => React.ReactNode }) {
const [on, setOn] = useState(false);
return <>{children(on, () => setOn(v => !v))}</>;
}
// usage
<Toggle>
{(on, toggle) => (
<div>
<button onClick={toggle}>{on ? 'Hide' : 'Show'}</button>
{on && <Modal />}
</div>
)}
</Toggle>Replaced by custom hooks
tsx
// the same MouseTracker logic as a hook - much cleaner
function useMousePosition() {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const handler = (e: MouseEvent) => setPos({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handler);
return () => window.removeEventListener('mousemove', handler);
}, []);
return pos;
}
function Component() {
const { x, y } = useMousePosition();
return <p>Mouse at {x}, {y}</p>;
}Render props solved logic sharing before hooks, but introduced deeply nested JSX. Custom hooks replaced most render prop patterns. You'll still encounter them as "children as a function" in libraries like Formik, Downshift and Headless UI (
{({ open }) => ...}).
updated
did you know it?