React Render Props vs Hooks
Compare render props and hooks as two ways to share reusable logic in React, with examples, mental models, and guidance on which pattern fits modern codebases.
What you'll learn
- ✓What render props solved
- ✓How hooks supersede most uses
- ✓When render props still shine
- ✓How to migrate between them
- ✓Performance and readability tradeoffs
Prerequisites
- •Familiar with React components
Render props and hooks are two answers to the same problem: how do you reuse stateful logic across components? This post compares them honestly, shows the same logic in both forms, and explains where each still belongs.
What and Why
Before hooks, React had no first-class way to share logic. Mixins were dead, and inheritance never fit. Render props filled the gap by letting a component own state and hand it to children through a function prop. The child decides how to render the data.
Hooks landed in React 16.8 and changed everything. A custom hook is just a function that calls other hooks. It returns values directly instead of via JSX, which removes nesting and ceremony. For most logic sharing today, hooks are the default and render props are the legacy choice.
Still, render props are not dead. They remain a clean way to share behavior that involves rendering, not just data.
Mental Model
A render prop says: I will manage the state, you tell me what to draw with it. A hook says: I will manage the state, you do whatever you want with the values I return. The first inverts control through JSX, the second through plain function returns.
Hooks compose linearly. You call them top to bottom in a function. Render props compose by nesting JSX, which creates pyramid shapes when you stack more than one.
Hands-on Example
Consider tracking the mouse position. Here it is as a render prop and as a hook.
render prop hook
<Mouse> const pos = useMouse();
{(pos) => ( return <Pointer pos={pos} />;
<Pointer pos={pos} />
)}
</Mouse> // Render prop version
class Mouse extends React.Component {
state = { x: 0, y: 0 };
handleMove = (e) => this.setState({ x: e.clientX, y: e.clientY });
render() {
return (
<div onMouseMove={this.handleMove}>
{this.props.children(this.state)}
</div>
);
}
}
// Usage
<Mouse>{(pos) => <p>{pos.x}, {pos.y}</p>}</Mouse>
// Hook version
function useMouse() {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const onMove = (e) => setPos({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', onMove);
return () => window.removeEventListener('mousemove', onMove);
}, []);
return pos;
}
// Usage
function Pointer() {
const pos = useMouse();
return <p>{pos.x}, {pos.y}</p>;
}
The hook version is shorter, easier to test, and composes with other hooks without nesting. The render prop version is more verbose but still readable.
Common Pitfalls
With render props, the deepest pitfall is performance. The function child is recreated on every render, breaking memoization for whoever consumes it. You also get pyramid nesting once you compose two or three render props together.
With hooks, the rules of hooks trip new developers up. You cannot call them conditionally or inside loops. Closures over stale state inside useEffect and useCallback lead to bugs that look like the framework misbehaving.
Mixing both in the same component without reason makes code hard to read. Pick one style per concern.
Best Practices
For new code, default to custom hooks. Prefix with use, return tuples or objects, and keep them focused. A hook that returns ten unrelated things is a smell.
Reach for render props when the pattern is genuinely about rendering, not data. Headless component libraries like Downshift and Radix often expose render props because the consumer truly controls markup.
If you maintain old code, migrate render props to hooks incrementally. Wrap the render-prop component in a thin hook adapter and update consumers one at a time.
Wrap-up
Hooks won the logic-sharing debate for good reason. They are shorter, more composable, and easier to test. Render props still have a place in headless libraries and rendering-focused APIs. Use the right tool for the job, but in 2026, the default is a hook.
Related articles
- React React Forms: Controlled vs Uncontrolled Inputs Explained
Understand the difference between controlled and uncontrolled inputs in React, when to use each, and how to combine them with refs and form libraries.
- React React.memo, useMemo, and useCallback Demystified
When memoization actually helps in React: the difference between React.memo, useMemo, and useCallback, and how to avoid memoizing for no reason.
- React React State Colocation Patterns: Where State Should Actually Live
A practical guide to deciding where state belongs in a React app, with patterns for lifting, colocating, and splitting state for performance and clarity.
- React Building Custom React Hooks
Extract stateful logic into reusable custom hooks. The use-prefix rule, three examples (useLocalStorage, useDebounce, useToggle), tuple vs object return values, and how to test them.