Render Props

[!NOTE] The term Render Prop refers to a technique for sharing code between React components using a prop whose value is a function.

The Concept

Instead of a component rendering its own hardcoded UI, it calls a function (passed as a prop) to determine what to render. This allows you to dynamically inject logic and state into whatever component you want.

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

Interactive Demo: Mouse Tracker

This is the classic example of Render Props. The Mouse component encapsulates the logic of tracking the cursor position. It exposes the x and y coordinates via a render prop, allowing the consumer to render anything based on that data (e.g., a crosshair, a cat, or just coordinates).

Render as:

Implementation

Let’s look at how to implement the Mouse component using a render prop.

1. The Logic Component

The Mouse component handles the state (x, y) and the event listeners. Instead of rendering UI, it calls props.render(state).

import React, { useState, MouseEvent, ReactNode } from 'react';

interface MouseState {
  x: number;
  y: number;
}

interface MouseProps {
  render: (state: MouseState) => ReactNode;
}

const Mouse = ({ render }: MouseProps) => {
  const [position, setPosition] = useState<MouseState>({ x: 0, y: 0 });

  const handleMouseMove = (event: MouseEvent<HTMLDivElement>) => {
    setPosition({
      x: event.clientX,
      y: event.clientY
    });
  };

  return (
    <div style={{ height: '100vh' }} onMouseMove={handleMouseMove}>
      {/* Delegate rendering to the prop! */}
      {render(position)}
    </div>
  );
};

2. The Consumer

Now we can use Mouse to render whatever we want using the coordinate data.

const App = () => {
  return (
    <div className="app">
      <h1>Move your mouse!</h1>

      {/* Render a Cat */}
      <Mouse render={({ x, y }) => (
        <span style={{ position: 'absolute', left: x, top: y }}>
          🐱
        </span>
      )} />

      {/* OR Render coordinates */}
      <Mouse render={({ x, y }) => (
        <p>Current position: {x}, {y}</p>
      )} />
    </div>
  );
};

Visualizing Data Flow

Parent Component <Mouse /> State: { x, y } Calls render(state) UI Output args

Children as a Function

Technically, you don’t need a prop named render. You can use the children prop!

<Mouse>
  {({ x, y }) => (
    <p>The position is {x}, {y}</p>
  )}
</Mouse>

Implementation:

const Mouse = ({ children }) => {
  // ... logic ...
  return children(position);
}

Render Props vs Hooks

Before React Hooks (v16.8), Render Props were the primary way to share logic. Now, Custom Hooks are usually preferred because they are cleaner and avoid “wrapper hell” or deep nesting in JSX.

With Hooks:

const { x, y } = useMousePosition();
return <div style={{ left: x, top: y }}>🐱</div>;

Why learn Render Props then?

  1. Libraries: Many popular libraries (like Formik, React Router, Downshift) still use render props for complex UI composition.
  2. Explicit Data Flow: Sometimes passing data explicitly via a function is clearer than implicit context or hooks.
  3. Dynamic Rendering: When you need to render something completely different based on state, a render prop is often more flexible than a hook + if/else statements.

Summary

  • Render Props enable logic reuse by passing a function as a prop.
  • The component handles logic and delegates rendering to the parent.
  • Often implemented using the children prop.
  • Largely replaced by Hooks for logic reuse, but still relevant for UI composition.