⚛️ React Q76 / 113

What is render props pattern?

AI-Powered Answer ✓ Answered

The render props pattern is a technique in React for sharing code between components using a prop whose value is a function. This function is then called by the component to render some UI, allowing the child component to control what gets rendered and how.

What are Render Props?

At its core, a "render prop" is a prop on a component that expects a function. The component then calls this function with its internal state or logic as arguments, and the function returns the JSX to be rendered. This allows the parent component (the one providing the render prop function) to define what UI should be rendered based on the child's internal state, without the child component itself needing to know how to render it.

This pattern is a powerful way to achieve cross-cutting concerns and reuse stateful logic or behavior across multiple components, similar to Higher-Order Components (HOCs) but often with greater flexibility and explicit data flow.

How it works (Example)

Let's consider a simple example: a component that tracks the current mouse position. Instead of rendering specific UI, it provides the mouse coordinates to a render prop.

jsx
import React from 'react';

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  };

  render() {
    // The 'render' prop is a function that receives the state
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)} 
      </div>
    );
  }
}

Now, any component can use this MouseTracker to get the mouse position and render whatever it wants based on that data:

jsx
import React from 'react';
import MouseTracker from './MouseTracker';

function App() {
  return (
    <div>
      <h1>Move the mouse around!</h1>
      <MouseTracker
        render={({ x, y }) => (
          <p>The current mouse position is ({x}, {y})</p>
        )}
      />
      
      {/* Another usage: render an image at mouse position */}
      <MouseTracker
        render={({ x, y }) => (
          <img 
            src="https://upload.wikimedia.org/wikipedia/commons/b/b2/Mouse-cursor-pointer.svg"
            alt="mouse pointer"
            style={{
              position: 'absolute',
              left: x,
              top: y,
              width: '24px'
            }}
          />
        )}
      />
    </div>
  );
}

Using `children` as a Render Prop

A common convention in React is to use the children prop as a render prop. If a component expects children to be a function, it can call that function to render its content. This is often preferred as it's more idiomatic for defining what a component "renders inside" of itself.

jsx
class MouseTracker extends React.Component {
  // ... same state and handleMouseMove ...

  render() {
    // The 'children' prop is a function that receives the state
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        {this.props.children(this.state)}
      </div>
    );
  }
}

// Usage with children as render prop
function App() {
  return (
    <div>
      <h1>Move the mouse around!</h1>
      <MouseTracker>
        {({ x, y }) => (
          <p>The current mouse position is ({x}, {y})</p>
        )}
      </MouseTracker>
    </div>
  );
}

Benefits

  • Reusability: Provides a highly reusable way to share stateful logic or functionality between components without duplication.
  • Flexibility: The consumer of the component has complete control over what UI is rendered, unlike HOCs which often abstract away the rendering logic.
  • Explicit Data Flow: It's very clear what data the wrapper component is providing to the renderer function.
  • No Naming Collisions: Unlike HOCs, which can introduce naming conflicts if multiple HOCs inject props with the same name, render props avoid this by passing the data directly to a function.
  • Avoids "Wrapper Hell": While HOCs can lead to deeply nested component trees in the React DevTools, render props typically result in a flatter tree for the rendered output.

Downsides

  • Can make the component tree deeper: While avoiding HOC wrapper hell, using many render props can still lead to a deeper JSX structure within a single component's render method, potentially impacting readability.
  • Performance Considerations: If the render prop function creates a new function on every render, it can cause unnecessary re-renders in pure components (though this is a common React optimization concern, often mitigated with React.memo or useCallback in Hooks).

Render Props vs. React Hooks

With the introduction of React Hooks, many use cases that previously relied on render props (or HOCs) for sharing stateful logic can now be solved more concisely and often more effectively using custom Hooks. For example, the MouseTracker functionality could be implemented as a custom useMousePosition Hook.

jsx
import React, { useState, useEffect } from 'react';

function useMousePosition() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

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

    window.addEventListener('mousemove', handleMouseMove);

    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, []);

  return position;
}

function App() {
  const { x, y } = useMousePosition();

  return (
    <div>
      <h1>Move the mouse around!</h1>
      <p>The current mouse position is ({x}, {y})</p>
    </div>
  );
}

While Hooks are generally the preferred modern approach for reusing logic, render props remain a valid and powerful pattern, especially in existing codebases or scenarios where you specifically want a component to delegate its rendering to a function prop for maximum flexibility over the UI output.