Why did custom hooks replace render props and higher-order components as React's default way to reuse stateful logic? Name the concrete problems with the older patterns.
answer
- reuse of logic paid for in tree nodes
- where did this prop come from?
- two wrappers, one prop name
- pyramid of callbacks in JSX
- hooks add no node, you name the result
basics
~20 sBoth older patterns reuse logic by adding components: HOCs stack into deep wrapper chains with silent prop collisions and hidden prop origins, and render props nest into callback pyramids. A hook is a plain function call whose result you name yourself, adding nothing to the tree.
solid answer
~60 sBoth older patterns solved logic reuse by wrapping components, and wrapping has a fixed set of costs. Stacked HOCs produce wrapper hell — a debugging tree ten nodes deep before you reach anything that renders — and because they inject props invisibly, you cannot tell from a component's source where `user` came from, or notice when two wrappers inject the same name. Render props avoid the tree depth problem for one use but nest into a pyramid as soon as you need three of them, and you cannot easily feed one callback's value into the next provider. Custom hooks reuse the *logic* without the wrapper: they add no node to the tree, the caller names each returned value at the point of use so collisions become impossible, and composing several is just several lines in the component body, each free to use the previous one's result. What did not change is that hooks cannot render anything, so wrappers survive wherever the reusable unit must own markup or a place in the tree.
go deeper
Be able to say that both old patterns share logic by wrapping components, while a custom hook is just a function you call inside your component, so the tree stays flat.
Name the concrete costs — wrapper depth, injected props with no visible origin, silent name collisions, nested callbacks — and show how calling hooks in sequence removes each one.
Show the limits too: hooks render nothing, so providers, boundaries and item renderers still need components, and repackaging logic does not change how often anything re-renders.
Argue it as a codebase-wide contract question — invisible prop injection at module boundaries is unversioned coupling between teams, which is the real reason to prefer explicit call-site composition.
## The shared premise of the old patterns Before hooks, state and lifecycle only existed inside components. So any technique for sharing stateful logic had to route it through a component: either you wrapped the consumer in a component that had the state (a HOC), or you rendered a component that had the state and let it call you back (a render prop). Both are the same trick with different ergonomics, and both inherit the same structural cost — reuse of *logic* is paid for in *tree nodes*. ## Cost 1: wrapper hell and lost provenance A screen assembled from five cross-cutting concerns ends up as five nested wrappers around one component that renders markup. Three things get worse together: - **Debugging.** The DevTools tree and stack traces fill with intermediate nodes, and finding the component that owns a piece of state means scrolling through wrappers. - **Provenance.** Open the component's source and you see it uses `props.currentUser` — but nothing in that file says where it came from. You must find the export statement at the bottom, read the composition chain, and work backwards. - **Collisions.** Two wrappers that both inject `data` collide silently. Whichever spreads last wins; nothing warns. Typing catches some of this, but only if the wrappers are typed precisely, which most legacy ones are not. ## Cost 2: the callback pyramid Render props push the same cost into JSX shape rather than tree depth at the module level: ```jsx <Auth> {(user) => ( <Theme> {(theme) => ( <Data userId={user.id}> {(rows) => <Table rows={rows} theme={theme} />} </Data> )} </Theme> )} </Auth> ``` The indentation grows with each concern, the ordering is load-bearing, and every value you need at the bottom must survive being threaded through the middle. It reads badly, but the deeper problem is that the composition is expressed in markup, so ordinary programming moves — extracting a variable, an early return, a conditional — are awkward or impossible. ## What a hook changes structurally ```jsx function Screen() { const user = useAuth(); const theme = useTheme(); const rows = useData(user.id); return <Table rows={rows} theme={theme} />; } ``` Compare that against both patterns above and the improvements are concrete rather than aesthetic: - **No node is added.** Three concerns, zero extra components, an unchanged tree. - **Names are chosen by the caller.** Collisions cannot occur, because you write `const user = ...` yourself. Provenance is on the same screen as the usage. - **Composition is ordinary code.** `useData(user.id)` consumes the previous line's value directly. In the HOC version, feeding one wrapper's injected prop into another wrapper's configuration requires an extra component in between; in the render-prop version it requires nesting in exactly the right order. - **Refs and statics stop being special.** Everything a wrapper had to hoist, forward or re-label simply does not arise, because there is no wrapper. ## What hooks did not fix Being honest about the limits is what separates a rehearsed answer from an understood one. - A hook renders nothing. Anything that must *be* in the tree — a provider, a boundary, a measured wrapper element, a row renderer invoked once per row — still needs a component, and often a function prop. - Hooks have their own discipline: fixed call order, no calls inside conditions or callbacks. HOCs and render props had no such rule. - Hooks do not reduce re-renders. Both old patterns and hooks re-render the consumer when the shared state changes; that is a separate concern from how the logic is packaged. - Sharing logic across components that must not share *state* still requires care either way — a custom hook gives each caller its own state, exactly as a separate wrapper instance did. ## The interview frame Neither older pattern is deprecated; there is no API to deprecate, since both are just conventions built on props. What changed is the default. In a React 19 codebase, logic that computes goes in a hook, and a wrapper appears only when something must sit in the tree. You will still read HOCs constantly in older application code and in libraries, so the expected answer is not "HOCs are bad" but a precise account of which costs disappear, which remain, and where a wrapper is still the correct shape.
- If hooks are better for logic reuse, why do component libraries still ship render props?Because a hook cannot render. When the library must own the markup or the subtree — a virtualized list deciding which rows exist, a measurement wrapper, a drag surface handing you live coordinates — the reusable unit has to be a component, and a function prop is how it hands control of the inner markup back to you. Many libraries ship both: a hook for the state, a component for the rendering.
- Does converting a HOC to a hook reduce the number of renders?No. Both re-render the consumer when the shared state changes; packaging is orthogonal to update frequency. The win is structural — no extra nodes, no injected-prop collisions, clearer provenance. If you were expecting a performance improvement, measure first, because the usual outcome is that render counts are identical.
- How would you catch two stacked HOCs injecting the same prop name today?Types are the practical answer: precisely typed wrappers make the collision a compile error rather than a silent overwrite. Beyond that it is convention — namespaced prop names, and a rule that a component takes at most one or two wrappers. The structural fix is to stop injecting props at all and call a hook, where the caller names each value.
saying these in an interview costs you the question
- Says hooks are syntactic sugar for HOCs
- Claims render props were removed from React
- Thinks converting to hooks reduces re-renders
- Believes HOCs are a deprecated React API
- Says hooks can replace every wrapper, including providers and boundaries