23.01.2026

React’te “Render Props”tan “Compound Components”e: Esnek UI API’leri Tasarlamak

Tek bir bileşenle farklı kullanım senaryolarını destekleyen, okunabilir ve esnek UI API’leri tasarlama rehberi.

React’te aynı bileşeni hem basit hem de ileri seviye senaryolarda kullandırmak istiyorsanız, API tasarımı kritik hale gelir. Bu yazıda iki güçlü desene odaklanacağız: Render Props ve Compound Components. Amaç: Tüketen ekipler için esnek, okunabilir ve “yanlış kullanımı zor” bir bileşen arayüzü.

1) Render Props: Kontrolü tüketene ver

Render props, bileşenin içindeki durumu/hesabı dışarı “fonksiyon” olarak verir. Tüketen taraf, UI’ı tamamen istediği gibi çizer.

Örnek: Klavye ile gezilebilen bir liste (aktif öğe yönetimi bileşende, render tüketende).

import React from "react";

type KeyboardListProps<T> = {
  items: T[];
  onSelect?: (item: T) => void;
  children: (api: {
    activeIndex: number;
    setActiveIndex: React.Dispatch<React.SetStateAction<number>>;
    getItemProps: (index: number) => React.HTMLAttributes<HTMLElement>;
  }) => React.ReactNode;
};

export function KeyboardList<T>({ items, onSelect, children }: KeyboardListProps<T>) {
  const [activeIndex, setActiveIndex] = React.useState(0);

  const getItemProps = (index: number) => ({
    role: "option",
    tabIndex: index === activeIndex ? 0 : -1,
    "aria-selected": index === activeIndex,
    onMouseEnter: () => setActiveIndex(index),
    onClick: () => onSelect?.(items[index]),
  });

  return (
    <div
      role="listbox"
      onKeyDown={(e) => {
        if (e.key === "ArrowDown") setActiveIndex((i) => Math.min(i + 1, items.length - 1));
        if (e.key === "ArrowUp") setActiveIndex((i) => Math.max(i - 1, 0));
        if (e.key === "Enter") onSelect?.(items[activeIndex]);
      }}
    >
      {children({ activeIndex, setActiveIndex, getItemProps })}
    </div>
  );
}

// Kullanım
// <KeyboardList items={data} onSelect={...}>
//   {({ getItemProps }) => (
//     <ul>
//       {data.map((x, i) => (
//         <li key={x.id} {...getItemProps(i)}>{x.title}</li>
//       ))}
//     </ul>
//   )}
// </KeyboardList>

Ne zaman iyi?

  • Bileşen “mantık” sağlar, UI tamamen farklılaşabilir.
  • Tasarım sistemi içinde farklı görünümler ama aynı davranış istenir.

Risk: Aşırı esneklik, kullanım karmaşıklığını artırabilir. Dokümantasyon şart.

2) Compound Components: Okunabilir ve kuralcı bir API

Compound components, tek bileşeni “alt bileşenler” ile kullanmayı teşvik eder: Tabs, Tabs.List, Tabs.Tab, Tabs.Panel gibi. İç iletişim genelde Context ile olur.

Örnek: Basit bir Tabs.

import React from "react";

type TabsContextValue = {
  value: string;
  setValue: (v: string) => void;
};

const TabsContext = React.createContext<TabsContextValue | null>(null);

function useTabs() {
  const ctx = React.useContext(TabsContext);
  if (!ctx) throw new Error("Tabs components must be used inside <Tabs>");
  return ctx;
}

type TabsProps = {
  defaultValue: string;
  children: React.ReactNode;
};

export function Tabs({ defaultValue, children }: TabsProps) {
  const [value, setValue] = React.useState(defaultValue);
  return <TabsContext.Provider value={{ value, setValue }}>{children}</TabsContext.Provider>;
}

Tabs.List = function TabsList({ children }: { children: React.ReactNode }) {
  return <div role="tablist">{children}</div>;
};

Tabs.Tab = function Tab({ value, children }: { value: string; children: React.ReactNode }) {
  const tabs = useTabs();
  const selected = tabs.value === value;
  return (
    <button
      role="tab"
      aria-selected={selected}
      onClick={() => tabs.setValue(value)}
    >
      {children}
    </button>
  );
};

Tabs.Panel = function Panel({ value, children }: { value: string; children: React.ReactNode }) {
  const tabs = useTabs();
  if (tabs.value !== value) return null;
  return <div role="tabpanel">{children}</div>;
};

// Kullanım
// <Tabs defaultValue="account">
//   <Tabs.List>
//     <Tabs.Tab value="account">Hesap</Tabs.Tab>
//     <Tabs.Tab value="security">Güvenlik</Tabs.Tab>
//   </Tabs.List>
//   <Tabs.Panel value="account">...</Tabs.Panel>
//   <Tabs.Panel value="security">...</Tabs.Panel>
// </Tabs>

Ne zaman iyi?

  • Okunabilirlik öncelikse (DOM yapısı “kendini anlatır”).
  • “Böyle kullanılmalı” dediğiniz bir bileşen ailesi varsa.

Risk: Alt bileşenleri yanlış yerde kullanma, SSR/portal gibi senaryolar. useTabs() ile erken hata vermek bu yüzden önemli.

3) Hangisini seçmeli?

  • UI tamamen değişecekse → Render Props
  • Kullanım şekli standart, okunabilir, yönlendirici olmalıysa → Compound Components
  • Hibrit de mümkün: Compound yapı + ileri seviye kaçış kapısı olarak render prop.

Son not: “API ergonomisi” bir performans optimizasyonudur

Doğru API, yanlış kullanımı azaltır; bu da bug’ı, refactor maliyetini ve onboarding süresini düşürür. React’te iyi bileşen tasarımı çoğu zaman “hangi hook?”tan daha fazla değer üretir.