The Render Props Pattern in React
The Render Props Pattern
~12 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Render props are the second major pre-hooks pattern for logic reuse – instead of a component (as with a HOC), a FUNCTION is passed as a prop, which React calls to determine WHAT gets rendered.
The basic idea: a function instead of JSX as a child
<DataLoader render={(data) => <Text>{data}</Text>} />
// or, more common: children ITSELF is the function ("function as children")
<DataLoader>{(data) => <Text>{data}</Text>}</DataLoader>The name "render prop" refers to ANY prop whose value is a function that returns React elements – whether it's called render or (the more common special case today) children itself is that function is purely a naming convention.
Building MouseTracker: a classic teaching example
We'll build the classic render-props teaching example – a component that tracks mouse position and passes it to any arbitrary presentation, WITHOUT knowing anything about what that presentation looks like:
import { useState, useEffect } from 'react';
function MouseTracker({ children }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMouseMove(event) {
setPosition({ x: event.clientX, y: event.clientY });
}
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return children(position);
}
export default MouseTracker;return children(position) is the key moment: instead of rendering {{children}} directly (as any normal component with children would), children here gets CALLED AS A FUNCTION, with position as the argument. The CALLER decides entirely for itself what to render from the mouse coordinates.
Usage: the same logic, two completely different presentations
Let's use MouseTracker on the home page, purely for demonstration – twice in a row, with DIFFERENT presentations of the same underlying logic:
import MouseTracker from '../components/MouseTracker';
// ... somewhere in the JSX, purely for demonstration:
<MouseTracker>
{(position) => <p>Mouse at: {position.x}, {position.y}</p>}
</MouseTracker>
<MouseTracker>
{(position) => (
<div
style={{
position: 'fixed',
left: position.x,
top: position.y,
width: 10,
height: 10,
borderRadius: '50%',
backgroundColor: 'red',
pointerEvents: 'none',
}}
/>
)}
</MouseTracker>The EXACT SAME MouseTracker logic (event listener, state, cleanup) gets reused – once as a text display, once as a red dot following the mouse. MouseTracker itself knows NOTHING about text or dots, only about coordinates.
The modern comparison: the same use case as a custom hook
// As a custom hook instead of a render prop:
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMouseMove(event) {
setPosition({ x: event.clientX, y: event.clientY });
}
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return position;
}
// Usage - noticeably less JSX nesting:
function MyComponent() {
const position = useMousePosition();
return <p>Mouse at: {position.x}, {position.y}</p>;
}| Pattern | Property |
|---|---|
| Render props | Adds an extra nesting level in the JSX ("wrapper hell" lite) – but works in ways not otherwise usable outside component functions, purely declarative and visible in the JSX tree. |
| Custom hooks | No JSX nesting, direct value access – BUT can only be called inside a function component, not used as a standalone, JSX-visible element. |
Achtung: A well-known trap with render props: an inline function as children ({{(position) => ...}}) creates a NEW function reference on EVERY render of MyComponent – exactly the chapter 31 problem with React.memo. If MouseTracker itself were wrapped in memo(), it would accomplish NOTHING as long as the render-prop function stays inline.
Tipp: Libraries that still actively use this pattern: <Formik> (older versions), react-motion, some charting libraries (D3 wrappers). For NEW code you write yourself, a custom hook is almost always the better choice today – this chapter's main purpose is to help you RECOGNIZE the pattern in someone else's code or older libraries, not to actively use it.