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:
SearchableListhandles searching and filtering, but doesn't know how to display each item (a place card, a plain string, anything). - The consumer passes
childrenas a function:{(item) => <Place item={item} />}. - The component calls
children(item)for every filtered item. - Generics (
<T>) keep the item type flowing fromitemsto 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
// 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.
// 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
placefromitems. Angular'slet-placeis typed asanyunless 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
| React | Angular |
|---|---|
children: (item: T) => ReactNode | contentChild(TemplateRef) |
{(place) => <Place item={place} />} | <ng-template let-place> |
children(item) | [ngTemplateOutlet] + { $implicit: item } |
Generic <T> | Generic class <T> |