Skip to content

Render Props

Render props pass a function as a prop (or child) that receives data from the provider component. The caller decides how to render using that data — flexible composition before hooks dominated.

Function as Prop

<Mouse render={({ x, y }) => <Cursor x={x} y={y} />} /> — Mouse tracks position; render prop decides UI. Same idea as children-as-function.

Custom hooks often replace render props today, but the pattern appears in libraries like Headless UI and React Router's <Route render>.

function DataLoader({ url, children }) {
  const [data, setData] = useState(null);
  useEffect(() => { fetch(url).then(r => r.json()).then(setData); }, [url]);
  return children({ data, loading: !data });
}

<DataLoader url="/api/user">
  {({ data, loading }) => loading ? <Spinner /> : <Profile user={data} />}
</DataLoader>

Children as function is a render prop variant.

Render Prop Variants

<Component render={(state) => <UI {...state} />} />
<Component>{(state) => <UI {...state} />}</Component>
  • Prop name need not be renderchildren as function is common.
  • Caller controls rendering; provider owns state/logic.
  • Avoid inline render functions in hot paths if memoization matters.
  • Hooks extract same logic with less JSX nesting.

Composition Patterns Compared

Render props, HOCs, and hooks.

Pattern Pros
Render props Explicit data flow in JSX
HOCs Wrap without changing JSX tree much
Custom hooks Cleanest for reuse today
Children slot Simple composition
Context Deep tree without prop drilling

Render Props in Libraries

Headless UI, Downshift, and Formik use render props or function children to give full UI control while owning behavior and accessibility.

Render Prop to Hook Refactor

Mouse tracker render prop becomes useMouse() hook returning { x, y } — component uses values directly without wrapper.

function useMouse() {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const move = (e) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', move);
    return () => window.removeEventListener('mousemove', move);
  }, []);
  return pos;
}

Common Mistakes

  • Deep nesting of multiple render prop components (callback pyramid).
  • Using render props when a simple hook suffices.
  • Inline render functions breaking memoization unnecessarily.
  • Confusing render props with controlled component props.

Key Takeaways

  • Render props share logic via function props/children.
  • Flexible UI control remains with the consumer.
  • Custom hooks are the modern equivalent for most cases.
  • Still used in headless component libraries.

Pro Tip

When you see {children(state)} in library source, recognize it as render props — pass a function child to customize UI.