Posts

Building Flexible UIs with the Render Props Pattern in React

January 10, 2025
The Render Props pattern is a powerful tool in React. It allows for maximum flexibility and reusability by sharing logic between components while letting consumers decide exactly how to render things. Although modern React leans heavily on hooks, Render Props remain relevant—especially when you want clear separation of concerns. Let’s dive into a real-world example: a generic List component using Render Props. A render prop is a prop (usually children) that’s a function. The component calls this function to render its output, passing in useful state or data. This gives consumers total control over how things are rendered, while encapsulating what is being done. You’re building a <List /> component that accepts an array of data and lets consumers decide how each item should be rendered.
Without Render Props
function List({ items }) {
  return (
      <ul>
      {items.map(item => (
          <li>{item.label}</li>
      ))}
      </ul>
  );
}
This works fine—until someone wants to:
  • Add an icon
  • Wrap items in a link
  • Style them differently
This tightly couples data structure and rendering. Let’s refactor using the Render Props pattern:
List Component with Render Props
type ListProps<T> = {
  items: T[];
  children: (item: T, index: number) => React.ReactNode;
};

{/* 
To enforce that each item has an id:

  type ListProps<T extends { id: string }> = {
      items: T[];
      children: (item: T, index: number) => React.ReactNode;
  };
*/}

function List<T>({ items, children }: ListProps<T>) {
  return (
      <ul>
          {items.map((item, index) => (
              <li key={index}>{children(item, index)}</li>
          ))}
      </ul>
  );
}
  • T is a generic type — this list can handle any data structure.
  • children is a function that receives each item and index.
Here’s how to use it for different cases:
Basic Usage Example
const users = [
  { id: 1, name: 'Alice', avatar: '👩' },
  { id: 2, name: 'Bob', avatar: '👨' },
];

<List items={users}>
  {(user) => (
      <div>
          <span>{user.avatar}</span> {user.name}
      </div>
  )}
</List>
Want to wrap each user in a link? No problem:
Wrapping Items in Links
<List items={users}>
  {(user) => (
      <a href={`/profile/${user.id}`}>
          {user.name}
      </a>
  )}
</List>
Now the List component stays generic, and rendering is completely controlled by the consumer.
  • Highly flexible — rendering is in your control.
  • 🔁 Logic reuse — the logic to loop, wrap in <ul>, handle keys stays inside.
  • 🧠 Generic-friendly — works perfectly with TypeScript generics.
You might be wondering, “Can’t I just use hooks?” Yes, but hooks are logic-only—they don’t handle rendering. Render Props combine:
  • Encapsulated logic
  • Render control
Think of them as the precursor to hooks for logic sharing, but they’re still useful when building libraries or headless UI components. Many libraries still embrace this pattern:
  • 🌀 Avoid deeply nested render props (callback hell).
The Render Props pattern lets you:
  • Build logic-heavy but UI-agnostic components.
  • Delegate rendering to the user.
  • Stay type-safe and generic with TypeScript.
Next, I will cover related patterns like:
On this page