Entwickler, der außergewöhnliche CRM- und Laravel-Lösungen liefert

Als erfahrener Entwickler spezialisiere ich mich auf Laravel- und Vue.js-Entwicklung, die Implementierung von Vtiger CRM sowie auf vielfältige WordPress-Projekte. Meine Arbeit zeichnet sich durch kreative, dynamische und benutzerzentrierte Weblösungen aus, die individuell an die Bedürfnisse meiner Kunden angepasst werden.

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 in React.js: Flexibilität durch Komponenten-Komposition

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.


Das Grundprinzip

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:

  • Wiederverwendbare Render-Logik
  • Volle Kontrolle über das Markup
  • Keine Namenskollisionen (im Gegensatz zu HOCs)

Praxisbeispiel: Generic List-Komponente

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-Daten
  • statusExtractor: Bestimmt Status-Badge
  • renderEntity: Optionale Render-Funktion für Custom-Markup

Test-Driven Development

Die 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();
  });
});

Anwendungsfall: Projektaufgaben

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:

  • API-Logik isoliert in useTasksFromProject
  • UI-Konsistenz durch zentrale List-Logik
  • Einfache Anpassung durch renderEntity-Prop

Wann Render Props sinnvoll sind

  1. Cross-Cutting Concerns:
    Logik, die über mehrere Komponenten hinweg genutzt wird (z.B. Listenhandling)
  2. UI-Variationen:
    Wenn unterschiedliche Darstellungen derselben Daten benötigt werden
  3. Library-Entwicklung:
    Maximale Flexibilität für Nutzer der Komponente

Best Practices

  • Type Safety:
    Generische Typen wie <P extends Entity> erhöhen Typsicherheit
  • Sinnvolle Defaults:
    Standard-Rendering für schnelle Nutzung ohne Konfiguration
  • Performance:
    Memoization von Render-Funktionen bei häufigem Re-Rendering
  • Kombination mit Hooks:
    State-Logik kann in Custom Hooks ausgelagert werden

Alternativen im Vergleich

PatternVorteileNachteile
Render PropsDirekte KompositionProp-Drilling möglich
HOCsWiederverwendbarkeitKomplexe Typinferenz
Custom HooksLogik-TeilungKein 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