©Sergey Emelyanov 2025 | Alle Rechte vorbehalten
Auch wenn Render Props heute seltener genutzt werden als zu Pre-Hooks-Zeiten, bleibt das Muster ein mächtiges Werkzeug zur Logikabstraktion. In einem aktuellen Projekt erwies es sich als unverzichtbar, um wiederkehrende Render-Logik zu zentralisieren – eine Erfahrung, die ich hier teilen möchte.
Render Props ermöglichen die Übergabe von Rendering-Logik als Prop an Kindkomponenten. Anders als HOCs (Higher-Order Components) erzwingen sie keine Komponentenhierarchie, sondern arbeiten mit direkter Komposition.
Schlüsselvorteile:
Unser Ziel: Eine generische ListEntities
-Komponente für Entity-Listen, die standardisiertes Rendering ermöglicht, aber individuelle Anpassungen zulässt.
1. Entity-Definition:
export type Entity = {
id: string;
assigned_user_id: string;
source: string;
label: string;
tags: string[];
starred: boolean;
description: string;
createdtime: string;
modifiedtime: string;
};
2. Die ListEntities-Komponente:
interface ListEntitiesProps<P extends Entity> {
entities: P[];
headerExtractor: (entity: P) => string;
statusExtractor: (entity: P) => string;
scrollCount?: number;
renderEntity?: (entity: P) => ReactNode;
}
export const ListEntities = <P extends Entity>({
entities,
headerExtractor,
statusExtractor,
renderEntity,
scrollCount = 3,
}: ListEntitiesProps<P>) => {
return (
<ScrollByCount count={scrollCount}>
{entities.map((entity) =>
renderEntity ? (
renderEntity(entity)
) : (
// Default-Rendering
<Card key={entity.id}>
{/* Standard-Layout */}
</Card>
)
)}
</ScrollByCount>
);
};
Key Props:
headerExtractor
: Extrahiert Header-DatenstatusExtractor
: Bestimmt Status-BadgerenderEntity
: Optionale Render-Funktion für Custom-MarkupDie Stateless-Natur vereinfacht Tests erheblich:
describe('ListEntities', () => {
const entities: Entity[] = [...];
test('Standard-Rendering', () => {
render(<ListEntities {...props} />);
expect(screen.getByText('Entity 1')).toBeInTheDocument();
});
test('Custom-Rendering via renderEntity', () => {
const customRender = (entity: Entity) => (
<div>Custom: {entity.label}</div>
);
render(<ListEntities renderEntity={customRender} ... />);
expect(screen.getByText('Custom: Entity 1')).toBeInTheDocument();
});
});
Konkrete Implementierung für Tasks:
export const ProjectTasks = ({ project }: ProjectTasksProps) => {
const { data, isLoading } = useTasksFromProject(project.id);
return (
<ListEntities
entities={data}
headerExtractor={(task) => task.projecttask_no}
statusExtractor={(task) => task.projecttaskstatus}
renderEntity={(task) => (
<Card>
{/* Custom Task-Layout mit NavLink */}
<NavLink to={`/tasks/${task.id}`}>
{task.projecttaskname}
</NavLink>
</Card>
)}
/>
);
};
Vorteile:
useTasksFromProject
<P extends Entity>
erhöhen TypsicherheitPattern | Vorteile | Nachteile |
---|---|---|
Render Props | Direkte Komposition | Prop-Drilling möglich |
HOCs | Wiederverwendbarkeit | Komplexe Typinferenz |
Custom Hooks | Logik-Teilung | Kein Markup-Management |
Fazit
Render Props sind keineswegs veraltet – im Gegenteil. In Szenarien, wo Komponenten variable Render-Logik benötigen, bieten sie eine elegante Lösung. Durch die Kombination mit TypeScript und modernen Hooks entstehen flexible, aber typsichere Komponenten.
"Gute Abstraktion erkennt man daran, dass sie Möglichkeiten eröffnet, anstatt Entscheidungen vorwegzunehmen."
– Unbekannter React-Entwickler
©Sergey Emelyanov 2025 | Alle Rechte vorbehalten