React
Render props vs hooks: which one wins
The hook wins when you need only state and behaviour; the render prop wins when the markup must stay with the component that hosts it.
Key points
- A hook returns values and functions, a render prop returns JSX: the second one also decides the structure.
- The render prop stays necessary when the library owns measurements, chart axes or drag handles.
- An inline function inside a render prop changes identity every render and defeats memo's comparison.
The hook wins in most cases, because it returns state and functions without imposing markup; the render prop wins when the structure must stay with the hosting component, as in a virtualised list or a chart.
What each one returns
The hook returns data and callbacks, and the caller writes the JSX. The render prop takes a function the component calls with its data, and that function returns the JSX: control of the structure crosses to the other side.
function useHover() {
const [hovered, setHovered] = useState(false);
const bind = {
onMouseEnter: () => setHovered(true),
onMouseLeave: () => setHovered(false),
};
return { hovered, bind };
}
function Hover({ children }) {
const [hovered, setHovered] = useState(false);
const bind = {
onMouseEnter: () => setHovered(true),
onMouseLeave: () => setHovered(false),
};
return children({ hovered, bind });
}
The difference in a table
| Need | Hook | Render prop |
|---|---|---|
| Reusable state | Yes, return value | Yes, function argument |
| Markup owned by the library | No | Yes |
| One extra level in the tree | No | Yes, a component |
| Isolated test | renderHook | render with a function |
When the render prop is the only choice
When the library owns the measurements: a virtualised list knows row height, a chart knows the axes, drag and drop knows the handles. The caller cannot rewrite that markup, so a function receiving the data is the only sensible contract.
Why the hook won the ordinary cases
A hook adds no component to the tree, needs no new function per render and composes with other hooks. Its return type is readable from the signature, and the test runs through renderHook without mounting markup.
The mistake that costs a render
An arrow function written inside the JSX changes identity on every pass: when the component is wrapped in memo, the prop comparison fails and the subtree redraws. With a hook the same risk sits in the dependency array, but it shows up in one place.
The command
Checking both shapes needs a test runner and a rendering library; the command installs the two packages and runs the suite.
npm install --save-dev vitest @testing-library/react
npx vitest run
Live records in the catalogue
The catalogue holds both shapes: use-mouse-position-ref is a hook published as a record, spotlight-card owns its markup and receives only the content, container-scroll-animation owns the scroll measurement. They are records among the 111,985 components.
Source and licence
React and @testing-library/react ship under the MIT licence. The React documentation still keeps a page on render props precisely because the pattern never disappeared: hooks cover the cases where markup is not in play.
The limitation
A hook cannot return JSX, so a library that must own the markup cannot be a hook: anyone exposing both shapes forces the caller to learn two contracts and choose without a single rule.
Where to continue
The calling rules are in rules of hooks; when deriving state removes the effect entirely is in derived state; the comparison with Vue composables is in Vue 3 Composition API.
Frequently asked questions
Can a component use hooks and render props together?
Yes, and it often helps: the hook holds the state, the render prop decides where the markup goes. Keep the passed function stable, or every render creates a new one.
Are render props deprecated?
No. React never deprecated them: hooks overtook them in ordinary cases, because a hook does the same job without an extra level in the tree.
Why can a hook not return JSX?
Because a hook is a data function, not a rendering one: React calls it during the component render, and JSX must come from a component. Returning JSX from a hook blurs the two roles.
Is a renderHook test enough?
For state and callbacks, yes. When the component using the hook has critical markup you also need a Testing Library test, because the hook never touches the DOM.
children or a render prop?
children when the content is static and the component places it; a render prop when the content depends on data the component computes, such as a row index or hover state.