Merhaba arkadaşlar, bugün başımı çok ağrıtan ve yeni başlayan herkesin mutlaka takıldığı bir konudan bahsedeceğim. React'ta useEffect hook'unu kullanırken, dependency array'ine (bağımlılık dizisi) bir obje koyduğumuzda neden sonsuz bir render döngüsüne giriyoruz? İlk başta ben de "Aynı objeyi koyuyorum işte, neden sürekli tetikleniyor?" diye kafayı yemiştim. Gelin bu sorunu birlikte çözelim.
Sorunun Kökeni: Referans Eşitliği
Temel sorun, JavaScript'teki referans eşitliği kavramı. React, useEffect'in dependency array'ini kontrol ederken shallow comparison (sığ karşılaştırma) yapar. Yani, array'in içindeki her bir değerin bir önceki render'daki değerle '===' operatörü ile aynı olup olmadığını kontrol eder.
İşte sorun tam da burada! Bir bileşen her render edildiğinde, içinde tanımlanan nesneler (object, array, function) yeni bir referans ile yeniden oluşturulur. Aynı anahtarlara (key) ve değerlere (value) sahip olsalar bile, bellekteki adresleri farklıdır. React bu iki farklı referansı "farklı" olarak görür ve useEffect'i yeniden çalıştırır. Bu da bileşeni tekrar render eder, bu render yeni bir obje oluşturur ve sonsuz bir döngü başlar.
Hemen basit bir örnekle gösterelim:
Bu kodu çalıştırırsanız, konsolun 'API isteği atılıyor...' yazısıyla dolup taştığını ve sayacın hızla arttığını görürsünüz. Çünkü her useEffect çalıştığında setSayac state'i güncelliyor, bu da bileşeni yeniden render ediyor, render sırasında filtreler objesi yeniden oluşuyor, useEffect bu yeni objeyi görüp tekrar tetikleniyor... Sonsuza kadar!
Çözüm Yolları: Kırılması Zor Değil!
Neyse ki panik yok, bu sorunu çözmenin birkaç temiz yolu var. İşte benim en sık kullandıklarım:
1. Objeyi State veya Ref İçinde Tutmak
Eğer obje, bileşenin yaşam döngüsü boyunca değişmeyecekse (sabit filtreler gibi), onu bileşenin dışına çıkarabilir veya useRef içinde saklayabilirsiniz. useRef'in .current değeri render'lar arasında değişmediği için sorun çözülür.
2. Dependency Array'ini Primitif Değerlere Ayırmak
En temiz ve React doktrinine uygun çözüm budur. Objenin içindeki primitif değerleri (string, number, boolean) dependency array'ine ayrı ayrı koymak. React bu değerleri karşılaştırmakta çok başarılıdır.
3. useMemo Hook'unu Kullanmak
Eğer objenin oluşturulması maliyetli bir işlemse veya başka state'lerden türetiliyorsa, useMemo kullanarak referansı koruyabiliriz. useMemo, bağımlılıkları (kategori, aktif) değişmediği sürece aynı objeyi döndürür.
Umarım bu açıklamalar ve çözümler, sizin de kafanızdaki soru işaretlerini gidermiştir. React'ın bu davranışı ilk başta can sıkıcı gelse de, aslında bizi daha tahmin edilebilir ve optimize bileşenler yazmaya zorlayan güzel bir detay.
Peki ya siz? useEffect ile ilgili benzer bir tuzakla karşılaştınız mı? Veya obje referanslarını yönetmek için farklı bir pattern kullanıyor musunuz? Yorumlarda deneyimlerinizi paylaşın, tartışalım!
Temel sorun, JavaScript'teki referans eşitliği kavramı. React, useEffect'in dependency array'ini kontrol ederken shallow comparison (sığ karşılaştırma) yapar. Yani, array'in içindeki her bir değerin bir önceki render'daki değerle '===' operatörü ile aynı olup olmadığını kontrol eder.
İşte sorun tam da burada! Bir bileşen her render edildiğinde, içinde tanımlanan nesneler (object, array, function) yeni bir referans ile yeniden oluşturulur. Aynı anahtarlara (key) ve değerlere (value) sahip olsalar bile, bellekteki adresleri farklıdır. React bu iki farklı referansı "farklı" olarak görür ve useEffect'i yeniden çalıştırır. Bu da bileşeni tekrar render eder, bu render yeni bir obje oluşturur ve sonsuz bir döngü başlar.
Hemen basit bir örnekle gösterelim:
JavaScript:
import { useEffect, useState } from 'react';
function SonsuzDonguBileseni() {
const [sayac, setSayac] = useState(0);
// 🚨 HER RENDER'DA YENİ BİR OBJE!
const filtreler = { kategori: 'teknoloji', aktif: true };
useEffect(() => {
console.log('API isteği atılıyor...', filtreler);
// Burada filtreler ile bir API isteği atıldığını düşünün.
setSayac(sayac + 1); // State değişimi bileşeni tekrar render ettirir.
}, [filtreler]); // 🚨 HER SEFERİNDE `filtreler` FARKLI BİR REFERANS!
console.log(`Bileşen ${sayac}. kez render edildi.`);
return <div>Render Sayacı: {sayac}</div>;
}
Bu kodu çalıştırırsanız, konsolun 'API isteği atılıyor...' yazısıyla dolup taştığını ve sayacın hızla arttığını görürsünüz. Çünkü her useEffect çalıştığında setSayac state'i güncelliyor, bu da bileşeni yeniden render ediyor, render sırasında filtreler objesi yeniden oluşuyor, useEffect bu yeni objeyi görüp tekrar tetikleniyor... Sonsuza kadar!
Neyse ki panik yok, bu sorunu çözmenin birkaç temiz yolu var. İşte benim en sık kullandıklarım:
Eğer obje, bileşenin yaşam döngüsü boyunca değişmeyecekse (sabit filtreler gibi), onu bileşenin dışına çıkarabilir veya useRef içinde saklayabilirsiniz. useRef'in .current değeri render'lar arasında değişmediği için sorun çözülür.
JavaScript:
import { useEffect, useRef, useState } from 'react';
function CozulmusBilesen() {
const [sayac, setSayac] = useState(0);
// ✅ useRef, render'lar arasında aynı referansı tutar.
const filtrelerRef = useRef({ kategori: 'teknoloji', aktif: true });
useEffect(() => {
console.log('API isteği atılıyor (useRef ile)...', filtrelerRef.current);
setSayac(sayac + 1);
}, [filtrelerRef.current]); // `.current` değeri asla değişmeyeceği için efekt SADECE BİR KEZ çalışır.
return <div>Render Sayacı: {sayac}</div>;
}
En temiz ve React doktrinine uygun çözüm budur. Objenin içindeki primitif değerleri (string, number, boolean) dependency array'ine ayrı ayrı koymak. React bu değerleri karşılaştırmakta çok başarılıdır.
JavaScript:
import { useEffect, useState } from 'react';
function EnIyiCozumBileseni() {
const [sayac, setSayac] = useState(0);
const [kategori, setKategori] = useState('teknoloji');
const [aktif, setAktif] = useState(true);
useEffect(() => {
// Objeyi burada oluşturabiliriz.
const filtreler = { kategori, aktif };
console.log('API isteği atılıyor (primitif değerlerle)...', filtreler);
setSayac(sayac + 1);
}, [kategori, aktif]); // ✅ Sadece primitif değerler. `kategori` veya `aktif` değişirse efekt çalışır.
return <div>Render Sayacı: {sayac}</div>;
}
Eğer objenin oluşturulması maliyetli bir işlemse veya başka state'lerden türetiliyorsa, useMemo kullanarak referansı koruyabiliriz. useMemo, bağımlılıkları (kategori, aktif) değişmediği sürece aynı objeyi döndürür.
JavaScript:
import { useEffect, useMemo, useState } from 'react';
function MemoCozumBileseni() {
const [sayac, setSayac] = useState(0);
const [kategori, setKategori] = useState('teknoloji');
const [aktif, setAktif] = useState(true);
// ✅ `kategori` veya `aktif` değişmediği sürece, aynı obje referansını döner.
const filtreler = useMemo(() => {
return { kategori, aktif };
}, [kategori, aktif]);
useEffect(() => {
console.log('API isteği atılıyor (useMemo ile)...', filtreler);
setSayac(sayac + 1);
}, [filtreler]); // ✅ Artık `filtreler` referansı, bağımlılıkları değişmedikçe sabit kalır.
return <div>Render Sayacı: {sayac}</div>;
}
Umarım bu açıklamalar ve çözümler, sizin de kafanızdaki soru işaretlerini gidermiştir. React'ın bu davranışı ilk başta can sıkıcı gelse de, aslında bizi daha tahmin edilebilir ve optimize bileşenler yazmaya zorlayan güzel bir detay.
Peki ya siz? useEffect ile ilgili benzer bir tuzakla karşılaştınız mı? Veya obje referanslarını yönetmek için farklı bir pattern kullanıyor musunuz? Yorumlarda deneyimlerinizi paylaşın, tartışalım!