İçeriğe geç
react

React'te Liste Render ve Key Prop'unun Önemi

Tarık Tunç
React'te Liste Render ve Key Prop'unun Önemi

React'te Liste Render ve Key Prop'unun Önemi

Veri listelerini render etmek, neredeyse her React uygulamasının temel bir gerekliliğidir. Kullanıcı listeleri, ürün katalogları, mesaj akışları ve tablolar gibi yapılar hep liste render'ı gerektirir. Bu yazıda React'te listelerin nasıl render edileceğini, key prop'unun neden bu kadar önemli olduğunu ve performans optimizasyonu tekniklerini detaylı olarak ele alacağız.

Temel Liste Render'ı: map() Metodu

React'te listeler, JavaScript'in Array.map() metodu kullanılarak render edilir:

function ProductList({ products }) {
  return (
    <div className="product-grid">
      {products.map(product => (
        <div key={product.id} className="product-card">
          <img src={product.image} alt={product.name} />
          <h3>{product.name}</h3>
          <p className="price">{product.price.toFixed(2)} TL</p>
          <span className="category">{product.category}</span>
        </div>
      ))}
    </div>
  );
}

// Kullanım
const products = [
  { id: 1, name: 'React Kitabı', price: 49.99, category: 'Kitap', image: '/img/react-book.jpg' },
  { id: 2, name: 'JavaScript Kursu', price: 199.99, category: 'Eğitim', image: '/img/js-course.jpg' },
  { id: 3, name: 'VS Code Eklentisi', price: 0, category: 'Araç', image: '/img/vscode.jpg' }
];

<ProductList products={products} />

Key Prop'u Neden Önemli?

Key, React'in reconciliation (uzlaştırma) sürecinde her liste elemanını benzersiz şekilde tanımasını sağlar. Doğru key kullanımı, React'in hangi elemanların değiştiğini, eklendiğini veya silindiğini verimli bir şekilde belirlemesini sağlar:

// YANLIŞ: Index'i key olarak kullanmak
{items.map((item, index) => (
  <li key={index}>{item.name}</li>  // Sorunlu!
))}

// DOĞRU: Benzersiz ve kalıcı bir değer kullanmak
{items.map(item => (
  <li key={item.id}>{item.name}</li>  // Doğru!
))}
Key'ler React'e her liste elemanının "kimliğini" söyler. Elemanlar yeniden sıralandığında, eklendiğinde veya silindiğinde React key'lere bakarak doğru DOM güncellemesini yapar. Yanlış key kullanımı beklenmeyen davranışlara ve performans sorunlarına yol açar.

Index Key'in Sorun Yarattığı Durum

function ReorderableList() {
  const [items, setItems] = useState([
    { id: 'a', text: 'Birinci' },
    { id: 'b', text: 'İkinci' },
    { id: 'c', text: 'Üçüncü' }
  ]);

  const moveToTop = (index) => {
    setItems(prev => {
      const updated = [...prev];
      const [item] = updated.splice(index, 1);
      updated.unshift(item);
      return updated;
    });
  };

  return (
    <ul>
      {items.map((item, index) => (
        // key={index} kullanılsaydı, input değerleri
        // yeniden sıralamada karışırdı
        <li key={item.id}>
          <input defaultValue={item.text} />
          <button onClick={() => moveToTop(index)}>Başa Taşı</button>
        </li>
      ))}
    </ul>
  );
}

Uyarı: Index'i key olarak kullanmak şu durumlarda sorun yaratır: liste yeniden sıralanabilir, listenin başına/ortasına eleman eklenebilir, eleman silinebilir, liste elemanları state içerir (input, checkbox vb.). Index yalnızca statik, değişmeyen ve sıralanmayan listeler için kabul edilebilir.

Key Seçim Stratejileri

// 1. Veritabanı ID'si - en iyi seçenek
{users.map(user => <UserCard key={user.id} user={user} />)}

// 2. Benzersiz alan kombinasyonu
{events.map(event => (
  <EventCard key={`${event.date}-${event.title}`} event={event} />
))}

// 3. crypto.randomUUID() - oluşturma anında (render'da DEĞİL)
const addItem = (text) => {
  setItems(prev => [...prev, { id: crypto.randomUUID(), text }]);
};

Filtreleme ve Sıralama

function FilterableTable({ data }) {
  const [filter, setFilter] = useState('');
  const [sortField, setSortField] = useState('name');
  const [sortDirection, setSortDirection] = useState('asc');

  const processedData = useMemo(() => {
    return data
      .filter(item =>
        item.name.toLowerCase().includes(filter.toLowerCase()) ||
        item.email.toLowerCase().includes(filter.toLowerCase())
      )
      .sort((a, b) => {
        const modifier = sortDirection === 'asc' ? 1 : -1;
        if (a[sortField] < b[sortField]) return -1 * modifier;
        if (a[sortField] > b[sortField]) return 1 * modifier;
        return 0;
      });
  }, [data, filter, sortField, sortDirection]);

  const handleSort = (field) => {
    if (sortField === field) {
      setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc');
    } else {
      setSortField(field);
      setSortDirection('asc');
    }
  };

  return (
    <div>
      <input
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filtrele..."
      />
      <table>
        <thead>
          <tr>
            <th onClick={() => handleSort('name')}>
              Ad {sortField === 'name' && (sortDirection === 'asc' ? '↑' : '↓')}
            </th>
            <th onClick={() => handleSort('email')}>
              E-posta {sortField === 'email' && (sortDirection === 'asc' ? '↑' : '↓')}
            </th>
          </tr>
        </thead>
        <tbody>
          {processedData.map(item => (
            <tr key={item.id}>
              <td>{item.name}</td>
              <td>{item.email}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <p>{processedData.length} / {data.length} sonuç</p>
    </div>
  );
}

Nested (İç İçe) Listeler

function CategoryList({ categories }) {
  return (
    <div>
      {categories.map(category => (
        <div key={category.id} className="category-section">
          <h3>{category.name}</h3>
          <ul>
            {category.items.map(item => (
              <li key={item.id}>{item.name} - {item.price} TL</li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
}

İpucu: Key'ler yalnızca kardeş elemanlar (siblings) arasında benzersiz olmalıdır. Farklı listelerde aynı key değerlerini kullanabilirsiniz. Key prop'u bileşene aktarılmaz; React tarafından dahili olarak kullanılır. Bileşende key değerine ihtiyacınız varsa ayrı bir prop olarak aktarın.

Boş Liste ve Yükleme Durumları

function DataList({ items, loading, error }) {
  if (loading) {
    return (
      <div className="skeleton-list">
        {Array.from({ length: 5 }).map((_, i) => (
          <div key={i} className="skeleton-item" />
        ))}
      </div>
    );
  }

  if (error) {
    return <div className="error">Hata: {error}</div>;
  }

  if (items.length === 0) {
    return (
      <div className="empty-state">
        <p>Henüz veri bulunmuyor.</p>
      </div>
    );
  }

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Sonuç

Liste render'ı React'in günlük kullanımının büyük bir parçasıdır. map() metodu ile listeleri render etmek, doğru key prop'u seçimi ile performansı optimize etmek ve filtreleme/sıralama işlemlerini useMemo ile verimli hale getirmek temel becerilerdir. Key seçiminde her zaman benzersiz ve kalıcı değerler kullanın, index'ten mümkün olduğunca kaçının.

Kaynaklar

İlgili Yazılar