React Patterns & Best PracticesEntry 6 of 10

Render Props

A component receives a function (often as children) and calls it to render part of its output. The component controls when and with what data; the consumer controls how it looks.

Notes

  • Course example: SearchableList handles searching and filtering, but doesn't know how to display each item (a place card, a plain string, anything).
  • The consumer passes children as a function: {(item) => <Place item={item} />}.
  • The component calls children(item) for every filtered item.
  • Generics (<T>) keep the item type flowing from items to the render function.

What / Why / When

What

Delegating rendering to the consumer through a function.

Why

One component's logic, any presentation.

When

Reusable lists, tables, data fetchers, or any logic with consumer-defined output.

Code compare

React
// components/SearchableList.tsximport { useState, type ChangeEvent, type ReactNode } from "react";​type SearchableListProps<T> = {  items: T[];  itemKeyFn: (item: T) => string | number; // Entry 7  children: (item: T) => ReactNode;};​export default function SearchableList<T>({ items, itemKeyFn, children }: SearchableListProps<T>) {  const [searchTerm, setSearchTerm] = useState("");​  const searchResults = items.filter((item) =>    JSON.stringify(item).toLowerCase().includes(searchTerm.toLowerCase()),  );​  function handleChange(event: ChangeEvent<HTMLInputElement>) {    setSearchTerm(event.target.value);  }​  return (    <div className="searchable-list">      <input type="search" placeholder="Search" onChange={handleChange} />      <ul>        {searchResults.map((item) => (          <li key={itemKeyFn(item)}>{children(item)}</li>        ))}      </ul>    </div>  );}

<ng-template> is Angular's render prop: the consumer defines a template, and the component renders it with ngTemplateOutlet, passing data as the template context.

Angular
// searchable-list.component.tsimport { Component, computed, contentChild, input, signal, TemplateRef } from "@angular/core";import { NgTemplateOutlet } from "@angular/common";​@Component({  selector: "app-searchable-list",  imports: [NgTemplateOutlet],  template: `    <div class="searchable-list">      <input type="search" placeholder="Search" (input)="onSearch($event)" />      <ul>        @for (item of searchResults(); track itemKeyFn()(item)) {          <li>            <ng-container              [ngTemplateOutlet]="itemTemplate()"              [ngTemplateOutletContext]="{ $implicit: item }"            />          </li>        }      </ul>    </div>  `,})export class SearchableListComponent<T> {  items = input.required<T[]>();  itemKeyFn = input.required<(item: T) => string | number>();  itemTemplate = contentChild.required<TemplateRef<{ $implicit: T }>>(TemplateRef);​  protected searchTerm = signal("");  protected searchResults = computed(() =>    this.items().filter((item) =>      JSON.stringify(item).toLowerCase().includes(this.searchTerm().toLowerCase()),    ),  );​  onSearch(event: Event) {    this.searchTerm.set((event.target as HTMLInputElement).value);  }}

Differences and gotchas

  • React render props are plain functions, so TypeScript infers place from items. Angular's let-place is typed as any unless you add a context type guard (static ngTemplateContextGuard).
  • Define key functions as class fields in Angular; inline arrow functions aren't allowed in templates.
  • Render props were once the main way to share logic in React; custom hooks replaced most uses. They remain the right tool when the component owns the logic and the consumer owns the markup.

Quick reference

ReactAngular
children: (item: T) => ReactNodecontentChild(TemplateRef)
{(place) => <Place item={place} />}<ng-template let-place>
children(item)[ngTemplateOutlet] + { $implicit: item }
Generic <T>Generic class <T>