Skip to content

Bölüm 5 / 16

Liste ve koşullu render

Liste render, key, koşullu render.

11 dakikalık okuma

Panel ekranlarının iki temel hareketi: bir diziyi satırlara çizmek ve "hangi durumda ne görünecek" kararını vermek. Angular bunlar için şablon sözdizimi verdi (@for/@if — 10. hattın 3. dersi); React'in cevabı 1. dersteki ilkeden çıkar: şablon dili yok, JavaScript var — döngü map, koşul ternary. Ama iki gerçek tuzak seni bekliyor: key olarak index vermenin state'i kaydırması (Angular track'te yaşadığın deneyin aynısı) ve &&'in ekrana "0" basması. İkisi de üretim koduna sızan, mülakatta sorulan hatalardır.

01Ne öğreneceksin

  • Liste render: map ile JSX dizisi; key'in reconciliation'daki gerçek rolü
  • key=index zararı: state kayması deneyi — Angular track $index ile aynı ders
  • key değiştirerek bilinçli sıfırlama: remount bir araçtır
  • Koşullu render kalıpları: erken return, ternary, && — ve "0" tuzağı
  • Yükleniyor / hata / veri / boş dörtlüsü — 9. hattın ayırt edici birleşimiyle tip güvenli ekran
  • Üretim pratiği: key'li Fragment, uzun listede sanal kaydırma kavramı

02map ile liste: key kimlik defteridir

JSX, süslü parantez içinde eleman dizisi kabul eder; liste render bu yüzden özel sözdizimi değil, bildiğin map'tir (8. hattın 15. dersi):

<ul>
  {basvurular.map(b => (
    <li key={b.id}>                        {/* key: React'in kimlik defteri */}
      <BasvuruKarti basvuru={b} onSil={sil} />
    </li>
  ))}
</ul>

key olmadan konsol uyarı basar; ama key bir "uyarıyı sustur" alanı değildir. 1. dersteki reconciliation'ı hatırla: React her render'da yeni tarifi eskisiyle kıyaslar. Listede sorun şudur — yeni dizide üç satır var, eskisinde de üç; ama hangisi hangisi? key, bu eşleştirmenin cevabıdır: React "aynı key = aynı eleman" der, elemanın DOM'unu ve state'ini korur, yalnız props'unu günceller. Angular köprüsü bire bir: @for'un track'i aynı soruyu cevaplıyordu (10. hattın 3. dersi) — orada performans vurguluydu, React'te işin içine state doğruluğu da girer; deneyi birazdan yapacağız.

03key=index: sessizce kayan state

En kolay kaçış key={i} yazmaktır — ve en pahalısı. Index kimlik değil konumdur; liste değişince konum el değiştirir:

{/* ❌ her satırında notu tutan input olan bir liste */}
{basvurular.map((b, i) => (
  <li key={i}>
    {b.unvan} <input placeholder="not al" />
  </li>
))}

{/* Senaryo: 1. satırın input'una "aradım, dönecekler" yazdın.
   Sonra 1. sıradaki başvuruyu SİLDİN.
   Eski liste:  key=0 (Sibersay), key=1 (Savunmatek)
   Yeni liste:  key=0 (Savunmatek)
   React: "key=0 hâlâ var" → DOM'u ve INPUT DEĞERİNİ korur,
   sadece metni değiştirir. Sonuç: notun artık SAVUNMATEK'in
   satırında duruyor — veri yanlış kayda yapıştı. */}

Hata sinsidir çünkü konsol tertemizdir — yanlışlık yalnız kullanıcının verisinde görünür. Aynısı satır içi state'te (açık/kapalı panel), animasyonda, seçim vurgusunda da olur. Kurallar:

  • Doğru key, verinin kalıcı kimliğidir: veritabanı id'si, benzersiz kod. Render sırasında üretilen değer (Math.random(), i) kimlik olamaz.
  • Math.random() en kötüsüdür: her render'da her key değişir → React her satırı söküp baştan kurar (state tamamen uçar, performans çöker).
  • index'in meşru olduğu dar alan: liste asla yeniden sıralanmıyor, filtrelenmiyor, ortadan silinmiyor ve satırların state'i yoksa (statik madde listesi) — o zaman index zararsızdır. Şüphedeysen id kullan.
  • key listede benzersizdir, globalde değil; ve key bir prop değildir — çocuğa props.key olarak geçmez, tamamen React'e aittir.

04key ile bilinçli sıfırlama: remount bir araçtır

Madem "key değişti = eski eleman öldü, yenisi doğdu" — bunu bilerek kullanabilirsin. Klasik ihtiyaç: detay panelindeki düzenleme formu, seçili başvuru değişince sıfırdan kurulmalı (eski taslak state'i taşınmamalı):

<BasvuruDuzenleForm key={secili.id} basvuru={secili} />
{/* secili.id değişti → React eski form örneğini söker, yenisini kurar:
   bütün iç state (taslak alanlar, touched bayrakları) temiz başlar. */}

Bu kalıp, 2. dersin "props state'e kopyalanmaz" kuralının şık istisnasını yönetir: form, basvuru'yu başlangıç değeri olarak state'ine alabilir — çünkü "hangi başvurunun taslağı" sorusunu artık key çözüyor. Effect'le "props değişince state'i resetle" yazmaya kalkmak (3. dersin kokusu) yerine tek öznitelik: key. Mülakat cümlesi: "remount'u bug değil, araç olarak kullanırım."

05Koşullu render: kalıplar ve "0" tuzağı

JSX ifade istediği için (1. ders) koşul araçların JavaScript'tir; dört kalıp işin tamamını görür:

// 1) erken return — bileşenin tamamı koşula bağlıysa
if (!secili) return <p>Bir başvuru seç.</p>;

// 2) ternary — iki dal da görünecekse
{gonderim.tur === "gonderiliyor" ? <Donuyor /> : <Kaydet />}

// 3) && — "varsa göster, yoksa hiçbir şey"
{hata && <p className="hata">{hata}</p>}

// 4) değişkene alma — dallar büyüyünce JSX'i okunur tutar
let icerik;
if (yukleniyor) icerik = <Iskelet />;
else if (hata)  icerik = <HataKutusu mesaj={hata} />;
else            icerik = <BasvuruListesi basvurular={veri} />;
return <section>{icerik}</section>;

Temel bilgi: JSX'te null, undefined, false ve true hiçbir şey render etmez — && kalıbı bu sayede çalışır. Ama "0" bu listede yoktur:

{/* ❌ mulakatSayisi === 0 iken ekranda çırılçıplak bir "0" belirir */}
{mulakatSayisi && <span className="rozet">{mulakatSayisi} mülakat</span>}

{/* neden: 0 falsy → && soldaki DEĞERİ döndürür (8. hat 13. ders)
   → JSX sayı 0'ı metin olarak basar. false basılmazdı, 0 basılır. */}

{/* ✅ koşulu boolean yap */}
{mulakatSayisi > 0 && <span className="rozet">{mulakatSayisi} mülakat</span>}

Aynı ailenin ikinci üyesi boş string'dir ("" && ... bir şey basmaz ama mantığı bulanıklaştırır). Ekip kuralı olarak benimse: &&'in solunda daima boolean ifade (> 0, !== "", !!x). Ternary kullanırken de "hiçbir şey" dalını null ile yaz.

06Dört durumlu ekran: tip güvenli birleşim

3. derste veri çekmeyi öğrendin; şimdi ekranın dürüstlüğünü kur. Bir liste ekranının dört hâli vardır ve boş, yükleniyor değildir: "sonuç yok" mesajı ile dönen çark ayrı şeylerdir. 9. hattın 4. dersindeki ayırt edici birleşim, dört hâli tek tipte toplar ve imkânsız kombinasyonları derleyiciye yasaklatır:

type ListeDurumu =
  | { tur: "yukleniyor" }
  | { tur: "hata"; mesaj: string }
  | { tur: "hazir"; basvurular: Basvuru[] };   // boşluk ayrı state değil: length === 0

const [durum, setDurum] = useState<ListeDurumu>({ tur: "yukleniyor" });

// render — switch ile daraltma (9. hat 4. ders):
switch (durum.tur) {
  case "yukleniyor": return <Iskelet />;
  case "hata":       return <HataKutusu mesaj={durum.mesaj} />;   // mesaj yalnız burada var
  case "hazir":
    if (durum.basvurular.length === 0)
      return <p>Filtreye uyan başvuru yok.</p>;    // BOŞ: dördüncü hâl, veri hâlinin içinde
    return (
      <ul>
        {durum.basvurular.map(b => <li key={b.id}>…</li>)}
      </ul>
    );
}

İki tasarım notu: (1) Filtre uygulanmış liste burada da türetilir (2. dersin kuralı) — durum.basvurular.filter(...) render'da; filtreye ayrı state açılmaz. (2) Bu üçlü durum yönetimini her ekranda elle yazmak, 10. derste TanStack Query'ye devredeceğin işin ta kendisidir (isPending / isError / data) — kütüphane geldiğinde bu deseni tanıyor olacaksın.

07Üretim pratiği: liste disiplini

  • Key'li Fragment: her satır iki kardeş element basacaksa (ör. dt + dd) kısa <> sözdizimi key alamaz — açık biçim gerekir: <Fragment key={b.id}>…</Fragment> (import { Fragment } from "react").
  • Satırı bileşene çıkar: map içi JSX üç-dört satırı geçince satır kendi bileşeni olur (BasvuruSatiri) — okunurluk bugün, React.memo ile render kırpma yarın (13. dersin konusu; memo'nun çalışma şartı olan referans kararlılığını 6. ders verecek).
  • Uzun liste = sanal kaydırma: 5.000 satırı DOM'a basmak, key doğru olsa da tarayıcıyı boğar (8. hattın 19. dersindeki render maliyeti). Çözüm yalnız görünen pencereyi çizmektir — Angular'da CDK virtual scroll'du (10. hattın 11. dersi), React ekosisteminde TanStack Virtual / react-window. Bu partide kurmuyoruz; adını ve ne zaman gerektiğini bil: "listem büyüyünce ne yaparsın?" mülakat sorusunun cevabı.
  • Sıralamayı yerinde yapma: dizi.sort state'i mutasyona uğratır (2. dersin tablosu) — render içinde toSorted / kopya-sonra-sort kullan; "sıraladım ama bazen bozuluyor" şikâyetinin klasik kaynağı budur.

08Mini alıştırma — Kimlik deneyleri

Yaklaşık 20 dakika · kurulum yok — StackBlitz projenin üstüne kur

  1. Başvuru listesine her satırda "not" input'u ekle (satır içi, controlled olmayan basit input yeter). Key olarak bilerek index ver; ilk satıra not yaz, ilk başvuruyu sil — notun alttaki kayda yapıştığını gör. Ekran görüntüsü alır gibi durumu bir cümleyle not et.
  2. Key'i b.id yap, deneyi tekrarla — notun sahibiyle birlikte silindiğini gör. İki davranış farkını reconciliation diliyle açıkla.
  3. Felaketi ölç: key'e Math.random() ver, input'a yazmaya çalış — her tuşta odağın kaçtığını (satır baştan kurulduğu için) yaşa; geri al.
  4. "0" tuzağını üret: {mulakatSayisi && <span>...</span>} yaz, sayıyı 0 yap, ekrandaki çıplak 0'ı gör; > 0 ile düzelt. false'un neden basılmayıp 0'ın basıldığını yorumla açıkla.
  5. Dört durumlu ekranı kur: ListeDurumu birleşimini yaz, sahte gecikmeli yükleme ile yukleniyor → hazir geçişini işlet; hata dalını da sahte reddedilen Promise ile test et. Boş filtre sonucunda "sonuç yok" mesajının (çark değil) çıktığını doğrula.
  6. Detay formuna key={secili.id} ver: iki başvuru arasında geçiş yaparken form alanlarının sıfırlandığını gör; key'i kaldırıp eski taslağın yanlış kayda taşındığını da gör — remount'un araç hâlini not et.

09Kendini sına

key reconciliation'da hangi soruyu cevaplar? "key listede benzersiz olsun" kuralının ötesinde neyi bilmek gerekir?

React her render'da yeni eleman tarifini eskisiyle kıyaslar; listede kritik soru "yeni dizinin hangi satırı, eskinin hangi satırıdır?" eşleştirmesidir. key bu kimlik defteridir: aynı key'i taşıyan eleman "aynı eleman" sayılır — DOM düğümü ve iç state'i korunur, yalnız props'u güncellenir; eski listede olup yenide olmayan key sökülür, yeni key sıfırdan kurulur. Bu yüzden key performans süsü değil doğruluk mekanizmasıdır: yanlış key, satır state'inin (input değeri, açık panel, seçim) yanlış kayda yapışması demektir. Bilinmesi gerekenler: key verinin kalıcı kimliğinden gelir (id), render'da üretilen değerden (index, Math.random) gelmez; benzersizlik yalnız o liste içinde aranır; key prop değildir — çocuk bileşene props.key olarak geçmez, tamamen React'in iç defterine yazılır. Angular'daki @for ... track aynı sorunun cevabıdır — iki çerçevede de kimlik, kimliği olan veriden gelir.

key=index neden zararlıdır — somut senaryoyla anlat. Hangi dar durumda kabul edilebilir, Math.random() neden en kötüsüdür?

Index kimlik değil konumdur; liste filtrelenince, sıralanınca ya da ortadan eleman silinince konumlar el değiştirir ama React "key=0 hâlâ burada" diye eski DOM'u ve state'i korur. Somut senaryo: her satırında not input'u olan listede ilk satıra not yazılır, ilk kayıt silinir — yeni listenin ilk sırasına yükselen başka kayıt, silinen kaydın input değerini devralır; konsol temizdir, yanlışlık yalnız kullanıcının verisindedir. Aynı kayma seçim vurgusu, açık/kapalı panel ve animasyonlarda da yaşanır. Index'in meşru dar alanı: liste asla yeniden sıralanmıyor/filtrelenmiyor/ortadan silinmiyor ve satırların iç state'i yok (statik madde listesi) — şüphe varsa id. Math.random() en kötüsüdür çünkü her render'da her key değişir: React tüm satırları söküp baştan kurar — bütün state uçar, odak kaçar, performans çöker. Bu, Angular'da track $index / track b.id seçiminde yaşadığın dersin aynısıdır.

JSX'te neler render edilmez? && kalıbının "0" tuzağını mekanizmasıyla açıkla ve ekip kuralını söyle.

JSX'te null, undefined, false ve true hiçbir şey render etmez — {hata && <p>...</p>} kalıbı bu sayede çalışır: koşul falsy'yse ifade false/null döner ve ekrana bir şey basılmaz. Tuzak, &&'in boolean döndürmemesinde: kısa devre operatörü soldaki değer falsy'yse o değeri döndürür. Sol taraf sayı 0 olduğunda ifadenin sonucu 0'dır ve 0 render edilir — "0 mülakat" rozetini gizlemek isterken ekranda çıplak bir 0 belirir; boş string aynı ailedendir. Çözüm ve ekip kuralı: &&'in solunda daima gerçek boolean ifade bulunur — mulakatSayisi > 0 && ..., metin !== "" && ..., gerekirse !!x. Ternary'de "hiçbir şey" dalı açıkça null yazılır; dallar büyüyünce koşul, JSX'in içinde değil değişkene alınmış if/else zincirinde kurulur — okunurluk da bir üretim değeridir.

Bir liste ekranının dört hâlini tip güvenli nasıl kurarsın? "Boş" hâli neden ayrı ele alınır ve key ile bilinçli sıfırlama ne zaman doğru araçtır?

Hâller ayırt edici birleşimle tek tipte toplanır: { tur: "yukleniyor" } | { tur: "hata"; mesaj } | { tur: "hazir"; basvurular } — böylece "hem yükleniyor hem hata" gibi imkânsız kombinasyonlar tipte yasaklanır ve switch daraltmasıyla her dalda yalnız o dalın alanlarına erişilir (hata mesajı yalnız hata dalında vardır). Boş, dördüncü görsel hâldir ama ayrı state değildir: hazir dalında length === 0 kontrolüdür — kullanıcıya dönen çark yerine "filtreye uyan sonuç yok" denir; boşluğu yükleniyormuş gibi göstermek UX yalanıdır. Filtrelenmiş liste de bu yapıda state değil render'da türetilen değerdir. Bu elle kurulan üçlü, TanStack Query'nin isPending/isError/data üçlüsünün ön çalışmasıdır. Key ile sıfırlama: "key değişti = eski örnek söküldü, yenisi kuruldu" davranışı bilinçli kullanılır — seçili kayıt değişince düzenleme formuna key={secili.id} vermek, bütün taslak state'ini temiz başlatır; "props değişince state'i effect'le resetle" anti-kalıbının tek öznitelikli doğru karşılığıdır.

10Sınav köşesi

Yazılıda nasıl sorulur

(1) React'te liste nasıl render edilir; key neden zorunludur? (2) key reconciliation'da nasıl kullanılır? (3) key olarak index vermenin sakıncası nedir; hangi durumda kabul edilebilir? (4) key={Math.random()} neye yol açar? (5) JSX'te hangi değerler render edilmez? (6) {sayi && <X />} kalıbının riski nedir? (7) Koşullu render için hangi kalıplar kullanılır? (8) Yükleniyor / hata / veri / boş durumları nasıl modellenir? (9) key değiştirerek bileşeni sıfırlamak ne zaman kullanılır? (10) Çok uzun listelerde hangi teknik uygulanır?

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Temel — "Liste map'tir, koşul JavaScript'tir — React şablon dili vermez. key ise reconciliation'ın kimlik defteridir: aynı key aynı eleman demektir, DOM ve state korunur." (2) Tuzak 1 — "Index konumdur, kimlik değil: silme/sıralamada satır state'i yanlış kayda yapışır — Angular'daki track dersinin aynısı; doğru key verinin kalıcı id'sidir, Math.random ise her render'da tam sökümdür." (3) Tuzak 2 — "&& soldaki falsy değeri döndürür; sol taraf 0 ise ekrana 0 basılır — solda daima boolean tutarım." (4) Model — "Ekranın hâllerini ayırt edici birleşimle kurarım: yükleniyor/hata/hazir, boş ise hazir'ın içinde ayrı mesajdır — çark değil. Bu, TanStack Query'nin bana vereceği üçlünün elle hâlidir." (5) Kıdem vuruşu — "key'i araç olarak da kullanırım: seçim değişince forma key veririm, remount taslağı sıfırlar; binlerce satırda sanal kaydırmaya geçerim; sort yerine toSorted — state'i yerinde mutasyondan korurum."