Merhaba arkadaşlar, bugün başımı çok ağrıtan ve özellikle React'ta asenkron işlemler yaparken hepimizin karşısına çıkma ihtimali yüksek olan bir sorundan bahsedeceğim. Bu hatayı ilk gördüğümde kafayı yemiştim, çünkü her şey doğru gibi görünüyordu ama konsol sürekli kırmızı uyarılarla doluyordu.
Karşılaştığım Sorun
Bir API'den veri çektiğimiz klasik bir senaryo düşünün. Kullanıcı bir butona tıklıyor, siz de `useEffect` içinde veya bir event handler'da `fetch` veya `axios` ile istek atıyorsunuz. Veri gelene kadar component'in kullanıcı tarafından başka bir sayfaya geçiş yapmasıyla DOM'dan kaldırıldığını (unmount) hayal edin. İşte tam da bu anda, gelen cevabı işlemeye çalışan kod, artık DOM'da olmayan bir component'in state'ini güncellemeye kalkışıyor.
React bize hemen şu meşhur uyarıyı fırlatıyor: "Can't perform a React state update on an unmounted component." Bu bir memory leak (bellek sızıntısı) uyarısıdır. Çünkü component yok olduğu halde, onun state'ini değiştirmeye çalışan bir fonksiyon hala çalışıyor ve bu da gereksiz bellek kullanımına ve potansiyel hatalara yol açıyor.
Temel Problemli Kod Örneği
İşte bu soruna davetiye çıkaran tipik bir kod parçası:
Gördüğünüz gibi, `fetchUser` fonksiyonu `await` ile cevabı bekliyor. Bu sırada component kaldırılırsa, gelen `data` ile `setUser` ve `setLoading` fonksiyonları çağrılmaya çalışılıyor. İşte felaket tam da burada başlıyor.
Kullandığım En Temiz Çözüm: Cleanup Function
React'ın bize bu tür sorunlar için harika bir çözümü var: `useEffect` hook'unun cleanup (temizleme) fonksiyonu. Bu fonksiyon, component unmount olmadan HEMEN önce veya dependency'ler değiştiğinde ve effect yeniden çalıştırılmadan önce çalıştırılır. Amacımız, bu cleanup anında, devam eden asenkron işlemleri iptal etmek veya en azından state güncellemesini engellemek.
İşte benim kullandığım en temiz çözüm:
Bu yöntemdeki anahtar nokta, `useRef` kullanarak component'in mount durumunu takip eden bir değişken (`isMounted`) oluşturmak. `useRef` değeri, component'in tüm render'ları boyunca kalıcıdır ve değişmesi yeniden render tetiklemez. Cleanup fonksiyonu çalıştığında bu değeri `false` yapıyoruz. State'i güncellemeden önce de mutlaka bu değeri kontrol ediyoruz.
Alternatif ve Modern Yaklaşım: AbortController
Eğer sadece uyarıyı susturmak değil de, gereksiz ağ isteklerini de tamamen iptal etmek istiyorsanız, `AbortController` API'si tam size göre. Bu yöntem özellikle büyük dosya indirmeleri veya sık aralıklarla değişen aramalarda çok faydalı.
Bu yöntem daha agresif ve temiz. Component unmount olur olmaz veya `userId` değişir değişmez, henüz tamamlanmamış olan `fetch` isteği sunucu tarafında bile iptal edilmeye çalışılır (tarayıcı ve sunucu desteğine bağlı olarak). Hem bellek hem de ağ trafiği açısından en verimli çözüm budur.
Sonuç olarak, bu memory leak uyarısı can sıkıcı görünse de, aslında React'ın bizi potansiyel performans ve bellek sorunlarına karşı uyaran güzel bir özelliği. `isMounted` ref'i ile kontrol veya `AbortController` kullanımı, profesyonel uygulamalar yazmak için olmazsa olmaz tekniklerden.
Siz bu sorunla nasıl başa çıkıyorsunuz? `useRef` yöntemini mi tercih edersiniz, yoksa doğrudan `AbortController` ile istekleri iptal etmeyi mi? Ya da React Query, SWR gibi data fetching kütüphaneleri kullanarak bu dertten tamamen kurtulduğunuzu mu düşünüyorsunuz? Yorumlarda deneyimlerinizi paylaşın!
Bir API'den veri çektiğimiz klasik bir senaryo düşünün. Kullanıcı bir butona tıklıyor, siz de `useEffect` içinde veya bir event handler'da `fetch` veya `axios` ile istek atıyorsunuz. Veri gelene kadar component'in kullanıcı tarafından başka bir sayfaya geçiş yapmasıyla DOM'dan kaldırıldığını (unmount) hayal edin. İşte tam da bu anda, gelen cevabı işlemeye çalışan kod, artık DOM'da olmayan bir component'in state'ini güncellemeye kalkışıyor.
React bize hemen şu meşhur uyarıyı fırlatıyor: "Can't perform a React state update on an unmounted component." Bu bir memory leak (bellek sızıntısı) uyarısıdır. Çünkü component yok olduğu halde, onun state'ini değiştirmeye çalışan bir fonksiyon hala çalışıyor ve bu da gereksiz bellek kullanımına ve potansiyel hatalara yol açıyor.
İşte bu soruna davetiye çıkaran tipik bir kod parçası:
JavaScript:
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchUser = async () => {
setLoading(true);
// Diyelim ki bu istek yavaş çalışıyor...
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
// Eğer kullanıcı bu sırada sayfadan ayrıldıysa, component unmount olur.
// Aşağıdaki satırlar çalıştığında artık bu component DOM'da YOK!
setUser(data); // ⚠️ BURASI HATA FIRLATIR!
setLoading(false);
};
fetchUser();
}, [userId]); // userId değişince yeniden çalışır
if (loading) return <p>Yükleniyor...</p>;
return <div>{user ? user.name : 'Kullanıcı bulunamadı'}</div>;
}
Gördüğünüz gibi, `fetchUser` fonksiyonu `await` ile cevabı bekliyor. Bu sırada component kaldırılırsa, gelen `data` ile `setUser` ve `setLoading` fonksiyonları çağrılmaya çalışılıyor. İşte felaket tam da burada başlıyor.
React'ın bize bu tür sorunlar için harika bir çözümü var: `useEffect` hook'unun cleanup (temizleme) fonksiyonu. Bu fonksiyon, component unmount olmadan HEMEN önce veya dependency'ler değiştiğinde ve effect yeniden çalıştırılmadan önce çalıştırılır. Amacımız, bu cleanup anında, devam eden asenkron işlemleri iptal etmek veya en azından state güncellemesini engellemek.
İşte benim kullandığım en temiz çözüm:
JavaScript:
import { useState, useEffect, useRef } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
// Component'in mount durumunu takip etmek için bir ref kullanıyoruz.
const isMounted = useRef(true);
useEffect(() => {
// Cleanup fonksiyonu: Component unmount olurken çalışır.
return () => {
isMounted.current = false;
};
}, []); // Boş dependency array: Sadece mount/unmount'ta çalışsın.
useEffect(() => {
// Her effect çalıştığında, component'in mount olduğundan emin olalım.
isMounted.current = true;
const fetchUser = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
// 🛡️ SİHİRLİ KONTROL: Eğer component hala mount durumdaysa state'i güncelle.
if (isMounted.current) {
setUser(data);
setLoading(false);
}
// Eğer unmount olmuşsa, bu blok çalışmaz, state güncellenmez, uyarı da olmaz!
} catch (error) {
// Hata durumunda da aynı kontrolü yapıyoruz.
if (isMounted.current) {
console.error('Veri çekilemedi:', error);
setLoading(false);
}
}
};
fetchUser();
// Bu effect için de cleanup: Eğer userId değişirse, önceki isteğin state güncellemesini engelle.
return () => {
isMounted.current = false;
};
}, [userId]); // userId değişince yeniden çalış
if (loading) return <p>Yükleniyor...</p>;
return <div>{user ? user.name : 'Kullanıcı bulunamadı'}</div>;
}
Bu yöntemdeki anahtar nokta, `useRef` kullanarak component'in mount durumunu takip eden bir değişken (`isMounted`) oluşturmak. `useRef` değeri, component'in tüm render'ları boyunca kalıcıdır ve değişmesi yeniden render tetiklemez. Cleanup fonksiyonu çalıştığında bu değeri `false` yapıyoruz. State'i güncellemeden önce de mutlaka bu değeri kontrol ediyoruz.
Eğer sadece uyarıyı susturmak değil de, gereksiz ağ isteklerini de tamamen iptal etmek istiyorsanız, `AbortController` API'si tam size göre. Bu yöntem özellikle büyük dosya indirmeleri veya sık aralıklarla değişen aramalarda çok faydalı.
JavaScript:
useEffect(() => {
// Yeni bir AbortController örneği oluştur.
const controller = new AbortController();
const signal = controller.signal;
const fetchUser = async () => {
setLoading(true);
try {
// Fetch isteğine abort sinyalini iletiyoruz.
const response = await fetch(`/api/users/${userId}`, { signal });
const data = await response.json();
setUser(data);
setLoading(false);
} catch (error) {
// Eğer hata abort'tan kaynaklandıysa, normal bir hata gibi işleme.
if (error.name === 'AbortError') {
console.log('Fetch işlemi iptal edildi:', userId);
} else {
console.error('Veri çekilemedi:', error);
setLoading(false);
}
}
};
fetchUser();
// Cleanup fonksiyonunda abort() çağır.
return () => {
controller.abort(); // ⏹️ Bu, devam eden fetch isteğini durdurur.
};
}, [userId]);
Bu yöntem daha agresif ve temiz. Component unmount olur olmaz veya `userId` değişir değişmez, henüz tamamlanmamış olan `fetch` isteği sunucu tarafında bile iptal edilmeye çalışılır (tarayıcı ve sunucu desteğine bağlı olarak). Hem bellek hem de ağ trafiği açısından en verimli çözüm budur.
Sonuç olarak, bu memory leak uyarısı can sıkıcı görünse de, aslında React'ın bizi potansiyel performans ve bellek sorunlarına karşı uyaran güzel bir özelliği. `isMounted` ref'i ile kontrol veya `AbortController` kullanımı, profesyonel uygulamalar yazmak için olmazsa olmaz tekniklerden.
Siz bu sorunla nasıl başa çıkıyorsunuz? `useRef` yöntemini mi tercih edersiniz, yoksa doğrudan `AbortController` ile istekleri iptal etmeyi mi? Ya da React Query, SWR gibi data fetching kütüphaneleri kullanarak bu dertten tamamen kurtulduğunuzu mu düşünüyorsunuz? Yorumlarda deneyimlerinizi paylaşın!