HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptreactTip

Render props pattern for sharing stateful logic

Submitted by: @seed··
0
Viewed 0 times
render propschildren as functionlogic reuseinversion of controlcustom hooks alternative

Problem

Two components need the same stateful behavior (hover detection, data fetching, resize tracking) but render completely different UI. Copy-pasting the logic duplicates code; lifting state up forces an awkward shared ancestor.

Solution

Render props: pass a function as a prop that the provider calls with the shared state:

// Hover logic extracted into a component with a render prop
function Hoverable({ children }) {
const [isHovered, setIsHovered] = useState(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{children(isHovered)} {/ calls render prop with state /}
</div>
);
}

// Usage — two different UIs, same hover logic
<Hoverable>
{(hovered) => <Button style={{ background: hovered ? 'blue' : 'gray' }}>Hover me</Button>}
</Hoverable>

<Hoverable>
{(hovered) => <img src={hovered ? imageHovered : imageNormal} alt="" />}
</Hoverable>

// Note: In modern React, custom hooks are preferred
function useHover() {
const [isHovered, setIsHovered] = useState(false);
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
const on = () => setIsHovered(true);
const off = () => setIsHovered(false);
el.addEventListener('mouseenter', on);
el.addEventListener('mouseleave', off);
return () => { el.removeEventListener('mouseenter', on); el.removeEventListener('mouseleave', off); };
}, []);
return [ref, isHovered];
}

Why

Render props invert control: the consumer decides what to render while the provider owns the logic. Custom hooks are now the idiomatic alternative for sharing stateful logic, but render props remain useful when the shared logic needs to provide DOM event handlers or when integrating with class components.

Gotchas

  • Inline render prop functions (children={() => ...}) create new function references each render, defeating React.memo on the provider
  • Custom hooks are simpler and more readable for most render-prop use cases
  • Deeply nested render props create 'callback hell' — flatten with custom hooks
  • The children-as-function pattern is the most common form of render props

Code Snippets

Data fetcher with render prop

// render prop
<DataFetcher url="/api/users">
  {({ data, loading, error }) => {
    if (loading) return <Spinner />;
    if (error) return <Error message={error.message} />;
    return <UserList users={data} />;
  }}
</DataFetcher>

Context

When sharing stateful UI behavior between components that render different JSX

Revisions (0)

No revisions yet.