React

What are render props in React?

The short answer

A render prop is a prop whose value is a function that returns React elements. Instead of a component deciding what to render on its own, it calls the function you pass it and renders whatever that function returns. This lets you share stateful logic between components without dictating what the UI should look like.

<MouseTracker
render={(position) => (
<p>
The mouse is at ({position.x}, {position.y})
</p>
)}
/>

The MouseTracker component tracks the mouse position. What gets rendered with that position? Whatever you decide by passing a function. The component owns the logic; you own the UI.

The problem it solves

Let's say you build a component that tracks the mouse position. It works great for showing coordinates on screen. But then another team wants to use that same tracking logic to move an image around the page. And another developer wants to use it to highlight sections of a heatmap.

The tracking logic is identical in all three cases — only the rendering is different. Without render props, your options were limited. You could duplicate the logic, or build a higher-order component, or use some other abstraction. Render props gave you a clean, explicit way to share the "what" (mouse position tracking) while letting the consumer decide the "how" (what to render with that data).

A practical example

Here's a MouseTracker component that tracks the cursor and exposes the position through a render prop:

class MouseTracker extends React.Component {
state = { x: 0, y: 0 };

handleMouseMove = (event) => {
this.setState({ x: event.clientX, y: event.clientY });
};

render() {
return (
<div
onMouseMove={this.handleMouseMove}
style={{ height: '100vh' }}
>
{this.props.render(this.state)}
</div>
);
}
}

Now different consumers can use the same logic with completely different UIs:

function App() {
return (
<div>
<MouseTracker
render={({ x, y }) => (
<p>
Mouse position: ({x}, {y})
</p>
)}
/>

<MouseTracker
render={({ x, y }) => (
<img
src="/cat.png"
alt="cat"
style={{
position: 'absolute',
left: x,
top: y,
}}
/>
)}
/>
</div>
);
}

One component, two completely different renderings. The logic is shared; the UI is flexible.

Children as a function

You don't have to call the prop render. A common variant uses children as the function, which gives you a slightly cleaner syntax:

class MouseTracker extends React.Component {
state = { x: 0, y: 0 };

handleMouseMove = (event) => {
this.setState({ x: event.clientX, y: event.clientY });
};

render() {
return (
<div
onMouseMove={this.handleMouseMove}
style={{ height: '100vh' }}
>
{this.props.children(this.state)}
</div>
);
}
}

function App() {
return (
<MouseTracker>
{({ x, y }) => (
<p>
The mouse is at ({x}, {y})
</p>
)}
</MouseTracker>
);
}

This is the same pattern — children is just a prop, and when its value is a function, you call it. Some people find this syntax more readable; others prefer naming the prop explicitly.

Render props vs higher-order components

Both render props and higher-order components (HOCs) solve the same fundamental problem: sharing logic between components. But they do it differently.

A HOC wraps your component and injects props into it. The problem is that it's not always clear where those props come from. When you stack multiple HOCs, prop name collisions become a real risk, and the component hierarchy in DevTools turns into a Russian nesting doll.

Render props are more explicit. You can see exactly what data is being passed because it's right there in the function arguments. There's no magic injection, no naming collisions, and the data flow is visible in the JSX.

// HOC — where does `mousePosition` come from? You have to read the HOC.
const EnhancedComponent = withMousePosition(MyComponent);

// Render prop — the data flow is right here in the JSX.
<MouseTracker
render={({ x, y }) => <MyComponent x={x} y={y} />}
/>;

Why custom hooks replaced render props

When React introduced hooks, the render prop pattern lost much of its purpose. The mouse tracking example becomes almost trivially simple with a custom hook:

function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });

useEffect(() => {
const handleMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
window.addEventListener('mousemove', handleMove);
return () =>
window.removeEventListener('mousemove', handleMove);
}, []);

return position;
}

function MouseDisplay() {
const { x, y } = useMousePosition();
return (
<p>
The mouse is at ({x}, {y})
</p>
);
}

No wrapper components, no nesting, no callback functions. The logic lives in a hook and can be used in any function component. It's simpler to read, simpler to compose, and doesn't add extra layers to the component tree.

When render props are still useful

Even in a hooks-first world, render props haven't disappeared entirely. You'll encounter them in:

  • Headless UI libraries — libraries like Downshift, React Aria, and older versions of Formik expose behavior through render props, giving you full control over the rendered output while the library handles the logic.
  • Inversion of control — when a library needs to give consumers complete rendering flexibility without knowing their component structure upfront.
  • Class component codebases — hooks only work in function components, so if you're working with an older codebase that uses class components, render props are still one of the best patterns for logic reuse.

The pattern also shows up implicitly in modern React. The children prop itself is a form of render prop — passing a function as children is the same mechanism, just normalized into everyday React usage.

Why interviewers ask this

This question probes your understanding of React's evolution and the different strategies for sharing logic between components. Interviewers want to see that you understand the problem render props solve, can implement the pattern, and know why hooks are typically preferred today. Bonus points if you can explain where render props still show up in modern libraries and why the pattern remains relevant even if you'd rarely write a new one from scratch.