useEffect Hook'u: Yan Etki Yönetiminin Tam Rehberi

useEffect Hook'u: Yan Etki Yönetiminin Tam Rehberi
React bileşenlerinin ana görevi UI render etmektir. Ancak gerçek dünya uygulamalarında API çağrıları, event listener'lar, zamanlayıcılar ve DOM manipülasyonları gibi "yan etkiler" (side effects) kaçınılmazdır. useEffect hook'u, bu yan etkileri güvenli ve kontrollü bir şekilde yönetmenizi sağlar. Bu rehberde useEffect'in çalışma mekanizmasını, dependency array'in inceliklerini ve yaygın tuzakları detaylı olarak ele alacağız.
useEffect'in Temel Yapısı
useEffect üç parçadan oluşur: setup fonksiyonu, cleanup fonksiyonu ve dependency array:
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 1. SETUP: Yan etki kodu
const controller = new AbortController();
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal
});
const data = await response.json();
setUser(data);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Kullanıcı çekilemedi:', error);
}
} finally {
setLoading(false);
}
};
fetchUser();
// 2. CLEANUP: Temizleme fonksiyonu
return () => {
controller.abort();
};
}, [userId]); // 3. DEPENDENCY ARRAY: Bağımlılıklar
if (loading) return <p>Yükleniyor...</p>;
if (!user) return <p>Kullanıcı bulunamadı</p>;
return <div><h2>{user.name}</h2><p>{user.email}</p></div>;
}
Dependency Array Davranışları
Dependency array, useEffect'in ne zaman çalışacağını belirler:
// 1. Dependency array YOK - her render'da çalışır
useEffect(() => {
console.log('Her render sonrası çalışır');
});
// 2. Boş dependency array - yalnızca mount'ta çalışır
useEffect(() => {
console.log('Yalnızca ilk render sonrası çalışır');
return () => console.log('Unmount olduğunda çalışır');
}, []);
// 3. Bağımlılıklarla - belirtilen değerler değiştiğinde çalışır
useEffect(() => {
console.log('userId veya role değiştiğinde çalışır');
return () => console.log('Yeni effect öncesi ve unmount\'ta çalışır');
}, [userId, role]);
Uyarı: Dependency array'i boş bırakmak ([]) ile hiç vermemek arasında büyük fark vardır. Boş array "yalnızca mount/unmount'ta çalış" anlamına gelirken, array'i hiç vermemek "her render'da çalış" anlamına gelir. Bu farkı gözden kaçırmak sonsuz döngülere neden olabilir.
Cleanup Fonksiyonu: Neden Önemli?
Cleanup fonksiyonu, memory leak'leri ve stale state sorunlarını önler. Şu durumlarda mutlaka kullanılmalıdır:
function LiveChat({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
// WebSocket bağlantısı
const ws = new WebSocket(`wss://chat.example.com/rooms/${roomId}`);
ws.onmessage = (event) => {
const newMessage = JSON.parse(event.data);
setMessages(prev => [...prev, newMessage]);
};
ws.onerror = (error) => {
console.error('WebSocket hatası:', error);
};
// Cleanup: Bağlantıyı kapat
return () => {
ws.close();
};
}, [roomId]);
return (
<div>
{messages.map((msg, i) => (
<p key={i}><strong>{msg.user}:</strong> {msg.text}</p>
))}
</div>
);
}
function WindowResize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
// Event listener ekleme
window.addEventListener('resize', handleResize);
// Cleanup: Event listener'ı kaldırma
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <p>Pencere: {windowSize.width} x {windowSize.height}</p>;
}
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// Interval başlatma
const intervalId = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
// Cleanup: Interval'i temizleme
return () => {
clearInterval(intervalId);
};
}, []);
return <p>Süre: {seconds} saniye</p>;
}
Race Condition Sorunu ve Çözümü
Ardışık API çağrılarında önceki çağrının sonucunun sonraki çağrının sonucunu ezmesi race condition sorununa yol açar:
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
// Race condition'ı önlemek için flag kullanımı
let isCancelled = false;
const search = async () => {
if (!query) {
setResults([]);
return;
}
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
// Effect temizlendiyse sonucu uygulamayı
if (!isCancelled) {
setResults(data.results);
}
} catch (error) {
if (!isCancelled) {
console.error('Arama hatası:', error);
}
}
};
search();
return () => {
isCancelled = true; // Önceki arama sonucunu yoksay
};
}, [query]);
return (
<ul>
{results.map(result => (
<li key={result.id}>{result.title}</li>
))}
</ul>
);
}
İpucu: Modern projelerde API çağrıları için useEffect yerine TanStack Query (React Query) veya SWR gibi kütüphaneler kullanmayı değerlendirin. Bu kütüphaneler caching, retry, race condition yönetimi ve daha fazlasını otomatik olarak yönetir.
useEffect'te Yapılan Yaygın Hatalar
1. Sonsuz Döngü
function BadExample() {
const [data, setData] = useState([]);
// HATALI: Her render'da yeni object/array oluşturulur
// Bu da useEffect'in sürekli çalışmasına neden olur
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(data => setData(data));
}); // dependency array yok = her render'da çalışır!
// HATALI: Object dependency
const options = { page: 1, limit: 10 }; // Her render'da yeni referans
useEffect(() => {
fetchData(options);
}, [options]); // Sonsuz döngü!
// DOĞRU: Değerleri ayrı ayrı bağımlılık yapın
const page = 1;
const limit = 10;
useEffect(() => {
fetchData({ page, limit });
}, [page, limit]);
}
2. Async Fonksiyon Doğrudan Kullanımı
// HATALI: useEffect callback'i doğrudan async olamaz
useEffect(async () => {
const data = await fetchData(); // Bu Promise döndürür, cleanup değil
setData(data);
}, []);
// DOĞRU: İç fonksiyon tanımlayın
useEffect(() => {
const loadData = async () => {
const data = await fetchData();
setData(data);
};
loadData();
}, []);
Bilgi: useEffect callback'i ya undefined ya da bir cleanup fonksiyonu döndürmelidir. Async fonksiyonlar her zaman Promise döndürdüğü için doğrudan useEffect callback'i olarak kullanılamazlar.
useEffect Çalışma Sırası
function LifecycleDemo() {
console.log('1. Render (fonksiyon gövdesi çalışır)');
useEffect(() => {
console.log('3. Effect çalışır (render sonrası)');
return () => {
console.log('2. Cleanup çalışır (yeni effect öncesi veya unmount)');
};
});
return <div>{console.log('2. JSX oluşturulur')}Demo</div>;
}
// İlk render: 1 → 2 → 3
// Sonraki renderlar: 1 → 2 → Cleanup → 3
// Unmount: Cleanup
Sonuç
useEffect, React'te yan etki yönetiminin temel aracıdır. Dependency array'in doğru kullanımı, cleanup fonksiyonlarının yazılması ve race condition'ların önlenmesi en kritik konulardır. Her useEffect kullandığınızda kendinize "Bu effect'in cleanup'a ihtiyacı var mı?" ve "Dependency array'im doğru mu?" sorularını sorun. Gereksiz useEffect kullanımından kaçının; çoğu hesaplama doğrudan render sırasında veya event handler'larda yapılabilir.