Yazılarımız

OfisData

REACT USEEFFECT HATALARINI ÖNLEMEK

React mavi atom logosu yanında büyük dependency array köşeli parantez kutusu boş içerik ve geri sarma cleanup ok sembolü minimal beyaz hero

useEffect dokümantasyonda iki satırla anlatılır; pratikte ise React projelerindeki bug'ların büyük çoğunluğu bu hook'un yanlış kullanımından gelir. Sonsuz döngüler, kaybolan abonelikler, eski state'i okuyan callback'ler, hızlı arama kutusunda yanlış sonucu gösteren race condition'lar.

Çoğu hatanın kaynağı tek bir yanlış zihinsel modeldir: useEffect bir "event handler" değildir; render'ın sonunda çalışan bir senkronizasyon mekanizmasıdır. Bu farkı görmeden yazılan kod, dependency array'i kandırarak sorunu öteler ama silmez.

useEffect'in gerçek amacı tek cümledir: React dünyasını dış dünyayla senkronlamak. Hataların beşi de bu cümleyi unutmaktan doğar — ve en değerli beceri, çözümleri bilmekten çok "burada useEffect'e hiç gerek yok" diyebilmektir.

useEffect Aslında Ne İçindir

useEffect, render sonucunu dış dünyayla senkronize etmek için vardır. "Dış dünya" demek: DOM API'leri, WebSocket, timer, abonelik, üçüncü parti kütüphane, ağ isteği gibi React'in kontrolü dışındaki her şey.

React mental modeli şudur: state değişti, component yeniden render edildi, sonra useEffect bu yeni state'in dış dünyaya yansıtılması için çalışır. Bu senkronizasyon zihniyeti ve "efekte gerek olmayan durumlar" ayrımı React öğrenme dokümantasyonunda ayrıntılı işlenir. Yani useEffect "şu olduğunda şunu yap" değildir; "şu state için dış dünya şu hâle gelmiş olmalı" der.

Bu fark netleşince useEffect'e yanlış görev verme refleksi azalır. Bir butona tıklandığında bir şey olacaksa, oraya useEffect değil event handler yazılır. Veriyi türetiyorsan useEffect'e değil, render sırasında basit hesaplamaya ihtiyacın var.

Hata 1: Sonsuz Render Döngüsü

En klasik tuzak. Effect içinde state set ediliyor, dependency array hatalı, sonuç: component sürekli yeniden render oluyor.

// YANLIŞ
useEffect(() => {
  setUser({ ...user, lastSeen: Date.now() });
}, [user]); // user her render'da yeni referans, effect sürekli tetiklenir

Çözüm önce şu soru: bu state'i gerçekten effect içinde mi güncellemen gerekiyor? Çoğu zaman cevap hayır. lastSeen bir kullanıcı eylemine bağlıysa onclick içinde set edilmeli. Eğer gerçekten dış olaya bağlıysa (mesela bir socket event), o zaman fonksiyonel updater kullan:

useEffect(() => {
  const id = setInterval(() => {
    setUser(prev => ({ ...prev, lastSeen: Date.now() }));
  }, 60000);
  return () => clearInterval(id);
}, []); // user'a bağımlı değil artık

Genel kural: state setter'larını dependency'ye eklemek zorunda kalıyorsan, fonksiyonel updater veya useReducer ile bağımlılığı kır.

Hata 2: Bağımlılık Dizisini Kandırmak

Lint uyarısı sinir bozucu olabilir. "Şu değişkeni eklemen lazım" der, sen de "biliyorum, kasıtlı bıraktım" deyip linter'ı susturursun. Genelde bu seçim yanlıştır.

// YANLIŞ
useEffect(() => {
  fetchData(filter);
}, []); // eslint-disable-next-line react-hooks/exhaustive-deps

filter değiştiğinde fetchData çağrılmıyor; eski filter ile veri çekiliyor. Bu "stale closure" sorunudur. Effect kapatıldığı andaki filter değerine takılı kalmış.

Doğru yaklaşım dependency'yi eklemek ve effect'in tetiklenme sıklığını yönetmek:

useEffect(() => {
  fetchData(filter);
}, [filter]);

Eğer effect çok sık tetikleniyorsa çözüm dependency'yi gizlemek değil; ya filter'ı debounce etmek ya effect'i React Query gibi bir mekanizmaya devretmek ya da fonksiyonu useEvent (React 19 ile gelen) ile sarmak.

Hata 3: Cleanup Yazmamak

Effect bir abonelik kuruyor, timer başlatıyor, event listener ekliyor ama temizlik fonksiyonu yok. Sonuç: component unmount olduğunda abonelik kalıyor, memory leak başlıyor, yeniden mount'ta listener iki kere bağlanıyor.

// YANLIŞ
useEffect(() => {
  window.addEventListener('resize', handleResize);
}, []);

// DOĞRU
useEffect(() => {
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);

Cleanup yalnızca unmount'ta değil, her yeni effect çalışmasından ÖNCE de tetiklenir. dependency değiştiğinde önce eski cleanup, sonra yeni effect koşar. Bu sayede subscribe/unsubscribe simetrisi otomatik korunur.

React 18'in strict mode geliştirme ortamında her effect'i iki kez çalıştırarak bu hatayı erkenden yakalatır. Strict mode'da "iki kere veri çekiyor" şikâyeti yerine "cleanup'ım tam mı" diye sormak doğru tepkidir.

VS Code dark theme useEffect cleanup örneği addEventListener resize ile return ifadesinde removeEventListener temizleme kodu syntax highlight

Hata 4: Race Condition

Arama kutusuna kullanıcı hızlı yazıyor. Her tuş için fetch atılıyor. "abc" için gelen yavaş response, "abcd" için gelen hızlı response'tan SONRA dönüyor. Sonuçta ekranda "abcd" yazıyor ama "abc"nin sonuçları görünüyor.

// YANLIŞ
useEffect(() => {
  fetch('/api/search?q=' + query)
    .then(r => r.json())
    .then(setResults);
}, [query]);

İki tipik çözüm var. İlki AbortController ile eski isteği iptal etmek:

useEffect(() => {
  const ctrl = new AbortController();
  fetch('/api/search?q=' + query, { signal: ctrl.signal })
    .then(r => r.json())
    .then(setResults)
    .catch(e => { if (e.name !== 'AbortError') throw e; });
  return () => ctrl.abort();
}, [query]);

İkincisi cleanup içinde bir bayrak set edip eski sonuçları yok saymak:

useEffect(() => {
  let cancelled = false;
  fetch('/api/search?q=' + query)
    .then(r => r.json())
    .then(data => { if (!cancelled) setResults(data); });
  return () => { cancelled = true; };
}, [query]);

Üretim kodunda elle bu kalıbı yazmak yerine React Query gibi bir kütüphaneye teslim etmek daha güvenli; race condition, retry, cache, deduplication hepsi gelir.

Hata 5: Türeyen Değerleri Effect ile Senkronlamak

Bir state'in başka bir state'ten türediğini fark etmemek useEffect'in en yaygın kötüye kullanımıdır.

// YANLIŞ
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);

useEffect(() => {
  setCount(items.length);
}, [items]);

count items.length'ten türeyen bir değer; ayrı state olmamalı. Doğru yaklaşım render sırasında doğrudan hesaplamak:

const [items, setItems] = useState([]);
const count = items.length;

Daha pahalı hesaplamalar için useMemo. Ama useEffect + setState kombosu burada hem ekstra render, hem stale state, hem gereksiz kompleksite getirir. Bu hata, React dokümantasyonundaki "you might not need an effect" sayfasının ana motivasyonudur.

useEffect'e Gerek Olmayan Durumlar

Aşağıdaki kalıpların hiçbirinde useEffect kullanma:

  • Türeyen değer: Mevcut state veya props'tan hesaplanabiliyorsa render içinde değişken olarak hesapla, gerekirse useMemo.
  • Event response: Buton tıklandığında bir şey olacaksa kod o handler'ın içinde olur, useEffect değil.
  • Prop değişince state sıfırlama: key prop'u ile component'i remount etmek genelde daha temiz çözüm.
  • Bağımsız state'leri senkronlama: İki state birbirinden türüyorsa biri state olmamalı, tek kaynak tut.
  • Prefetch / cache: React Query, SWR gibi araçlar bu işi useEffect'ten çok daha güvenli yapar.

useEffect'in Gerçekten Doğru Olduğu Durumlar

useEffect'in yeri darsa şu örnekler yeridir:

  • DOM API ile etkileşim (focus, scroll, document.title gibi)
  • WebSocket veya server-sent event aboneliği
  • setInterval, setTimeout zamanlayıcısı
  • window.addEventListener (resize, online/offline gibi global olaylar)
  • Üçüncü parti kütüphane init/destroy (chart kütüphanesi, harita SDK'sı)
  • Logging / analytics dispatch (component göründüğünde page view gönderme)

Bu örneklerin ortak özelliği: hepsi React'in dışındaki bir sisteme bağlanıyor. useEffect'in tam tanımı bu.

Bağımlılık Dizisini Doğru Yönetmek

Linter'ın "exhaustive-deps" kuralını kapatmak değil, beslenmek doğru yol. Bağımlılığa eklemen gerektiğinde değişkenin tipine göre strateji değişir:

  • Primitive değer (number, string, boolean): Doğrudan ekle, gerekiyorsa debounce ile sıklığı azalt.
  • Object / array: Parent'ta useMemo veya useState ile sabit referans tut, her render'da yeni nesne türetme.
  • Function: useCallback ile referansı stabilize et veya effect içine taşı.
  • Sadece okuma, tetikleyici değil: useEffectEvent (React 19) ile değer "latest" olarak okunabilir, bağımlılığa girmez.

Geliştirme Süreci Pratiği

useEffect yazarken kendine sırasıyla şu soruları sor:

  1. Bu gerçekten effect mi yoksa event handler'a mı taşımalıyım?
  2. Bu state başka bir state'ten türemiyor mu?
  3. Cleanup yazdım mı; abonelik/timer/listener varsa simetrik bırakıyor mu?
  4. Bağımlılık dizim doğru ve eksiksiz mi; linter susturma var mı?
  5. Race condition riski varsa AbortController veya cancel flag ekledim mi?
  6. Bu işi React Query, Zustand veya başka bir araç daha iyi yapmaz mı?

Hook'ların doğru kullanımı, dependency yönetimi ve modern alternatifler için React eğitim içeriği detaylı bir referans sunar.

useEffect karar checklist diyagramı 6 numaralı soru satır effect mi event mi türeyen mi cleanup var mı dependency tam mı race var mı kütüphane uygun mu

React 19 ile Gelen Yeni Araçlar

React 19 useEffect kaynaklı yaygın sorunları doğrudan hedefleyen iki yeni mekanizma getirdi:

  • useEffectEvent: Effect içinden çağrılan bir fonksiyonun "en güncel" değerleri okuması gerektiğinde, bağımlılığa eklemeden tutulmasını sağlar. Stale closure ve gereksiz tetiklemeyi çözer.
  • Actions ve useActionState: Form submit, mutation gibi event-driven işler için useEffect'e gerek bırakmıyor. Bu işler artık useEffect mantığından tamamen çıkıyor.

Bu yeniliklerle birlikte useEffect'in alanı daha da daralıyor. Artık gerçekten "React dışı sisteme bağlanma" görevine indirgeniyor; geri kalan her şey için daha uygun araçlar var.

Test Etmek

useEffect içeren kodu test ederken iki nokta önemli:

  • act() ile sarma: useEffect render sonrası çalıştığı için testlerde act() kullanılmadığında effect tetiklenmemiş olabilir. React Testing Library'nin render() ve userEvent fonksiyonları zaten act() ile sarılı gelir; manuel kullanımlarda dikkat.
  • Cleanup'ı doğrulamak: unmount() çağırdıktan sonra ekstra setState veya fetch çağrısı uyarı verir. Bu uyarıları yakalayıp test fail etmek, eksik cleanup'ı CI'da görmeyi sağlar.

Doğru useEffect; gözden kaybolan, fark edilmeden işini yapan effect'tir. Sürekli debug ettiğin, dependency'sini değiştirmek zorunda kaldığın, lint uyarısı verdiren effect; muhtemelen yanlış yerde durur. Önce "buraya gerçekten useEffect mi gerek" diye sor; çoğu zaman cevap "hayır" olur.

 Vimaj