LearnContact
Lesson 4722 min read

Render Props

Learn the render props pattern for sharing logic between components by passing a function as a prop, and see how it compares to HOCs and custom Hooks.

Introduction

In the previous lesson, you saw Higher-Order Components used to share logic between components by wrapping them. Render props is a second, closely related pattern that solves the exact same problem — reusing logic — but does so through composition rather than wrapping.

Instead of a function that returns a new component, a render prop component takes a function as a prop and calls that function to determine what to render. This keeps everything visible in a single JSX tree, rather than hidden inside a wrapper.

What You Will Learn
  • What the render props pattern is and how it works.
  • How to build a MouseTracker component using a render prop.
  • The related children-as-a-function variation.
  • How render props compare to HOCs and custom Hooks.
  • Why custom Hooks are generally preferred for new code today.

What are Render Props?

A render prop is a prop whose value is a function, and which the component calls during rendering to know what JSX to produce. Rather than hardcoding its own output, the component provides some data (or behavior) and lets the caller decide how to display it.

The Shape of a Render Prop Component
function DataProvider({ render }) {
  const data = useSomeData();
  return render(data); // calling the function prop
}

// Usage:
<DataProvider render={(data) => <p>{data}</p>} />

A Prop That Is a Function

The "render prop" itself is just a function passed in like any other prop.

Logic Stays Visible

Unlike HOCs, the composition happens directly in JSX, so it is easy to trace.

Reuses Behavior

The same data/behavior-providing component can drive many different visual outputs.

No Extra Wrapper Layer

There is no separately-named wrapped component sitting in the component tree.

Building a MouseTracker

A classic example is a component that tracks the mouse position and shares it via a render prop, letting different consumers decide how to display that position.

MouseTracker.jsx
function MouseTracker({ render }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });

  function handleMouseMove(event) {
    setPos({ x: event.clientX, y: event.clientY });
  }

  return (
    <div onMouseMove={handleMouseMove} style={{ height: "200px" }}>
      {render(pos)}
    </div>
  );
}

Now any consumer can render the mouse position however it wants, without MouseTracker knowing or caring about the presentation:

App.jsx
function App() {
  return (
    <MouseTracker
      render={(pos) => (
        <p>The mouse is at ({pos.x}, {pos.y})</p>
      )}
    />
  );
}
Rendered While Moving the Mouse
The mouse is at (128, 64)

(updates continuously as the mouse moves)
Reusing the Same Logic Differently

You could reuse the same MouseTracker with a completely different render function — for example one that positions a tooltip or draws a custom cursor — without touching MouseTracker itself.

Using children as a Function

A common variation passes the function as children instead of a named render prop. This reads a little more naturally in JSX, since the function is written between the opening and closing tags.

Children as a Function
function MouseTracker({ children }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });

  function handleMouseMove(event) {
    setPos({ x: event.clientX, y: event.clientY });
  }

  return (
    <div onMouseMove={handleMouseMove} style={{ height: "200px" }}>
      {children(pos)}
    </div>
  );
}

// Usage:
<MouseTracker>
  {(pos) => <p>{pos.x}, {pos.y}</p>}
</MouseTracker>

Render Props vs HOCs vs Hooks

HOCs, render props, and custom Hooks all exist to solve the same core problem: sharing stateful logic between components without duplicating code. They differ mainly in how that sharing is expressed.

Higher-Order Component

Wraps a component and returns a new one.

  • withMouse(Component)
  • Adds an extra layer in the tree
  • Naming collisions possible

Render Props

Passes a function as a prop; the function decides what to render.

  • <MouseTracker render={...} />
  • Logic stays visible in JSX
  • Can lead to nested callbacks ("wrapper hell" in JSX)

Custom Hook

Extracts logic into a plain function you call inside a component.

  • const pos = useMousePosition();
  • No extra component or wrapper
  • Generally preferred for new code

Custom Hooks are now generally preferred because they let you reuse logic without adding any extra components to the tree at all — you simply call a function and get values back, directly inside the component that needs them.

The Same Idea as a Custom Hook
function useMousePosition() {
  const [pos, setPos] = useState({ x: 0, y: 0 });

  useEffect(() => {
    function handleMouseMove(event) {
      setPos({ x: event.clientX, y: event.clientY });
    }
    window.addEventListener("mousemove", handleMouseMove);
    return () => window.removeEventListener("mousemove", handleMouseMove);
  }, []);

  return pos;
}

function App() {
  const pos = useMousePosition();
  return <p>{pos.x}, {pos.y}</p>;
}

Common Mistakes

Avoid These Mistakes
  • Defining the render function inline as a brand-new arrow function on every render when performance matters, causing unnecessary re-renders of the tracked child.
  • Nesting several render-prop components, which quickly becomes hard to read (sometimes called "callback hell" in JSX).
  • Forgetting that the render/children prop must be called (invoked) — passing it but never calling it renders nothing.
  • Reaching for render props for new code when a simpler custom Hook would do the same job with less nesting.

Best Practices

  • Prefer a custom Hook for new logic-sharing needs; reach for render props mainly when you must share visual/JSX-producing logic that a Hook cannot express.
  • Keep the function passed as a prop small and focused on a single piece of UI.
  • Name the prop render or children to match community convention.
  • Recognize render props when reading library documentation or legacy code, even if you rarely write new ones.

Frequently Asked Questions

Is render props the same as passing children?

It is related. The "children as a function" variation uses the children prop, but instead of JSX, it holds a function that the component calls and passes data into.

Do render props cause performance problems?

They can, if the function is redefined on every render and passed to a memoized child, since a new function reference breaks memoization. This is the same issue useCallback addresses elsewhere.

Are render props outdated?

They are less commonly written in new code since custom Hooks arrived, but you will still see them in some libraries, and they remain a valid, well-understood pattern.

Can a component accept more than one render prop?

Yes, a component can accept multiple function props (e.g. renderHeader, renderFooter) if it needs to let the caller customize several parts of its output.

Which should I learn first, HOCs, render props, or Hooks?

For writing new code, focus on custom Hooks. Understanding HOCs and render props is still valuable for reading existing codebases and libraries.

Key Takeaways

  • Render props is a pattern where a component receives a function as a prop and calls it to determine what to render.
  • The MouseTracker example shares mouse-position logic while letting the caller control the output.
  • The children-as-a-function variation passes the function via the children prop instead.
  • HOCs, render props, and custom Hooks are three different ways to share logic between components.
  • Custom Hooks are generally preferred for new code because they avoid extra wrapper components.

Summary

Render props let a component share logic by handing control of rendering to a function passed in as a prop, as shown by the MouseTracker example. You compared this to Higher-Order Components and to custom Hooks, the modern preferred approach.

Next, you will look at React.memo, a tool for skipping unnecessary re-renders of components whose props have not changed.

Lesson 47 Completed
  • You understand the render props pattern.
  • You built a MouseTracker using a render prop.
  • You can compare render props, HOCs, and custom Hooks.
  • You know why Hooks are generally preferred today.
Next Lesson →

React.memo