Custom Hook Yazmayalım mı?
React’te “Custom Hook” yazmak, tekrar eden kodları soyutlamanın en güçlü yolu.
Ama yanlış yerde useEffect kullanmak, seni kolayca sonsuz döngüler, memory leak’ler veya veri tutarsızlıklarıyla baş başa bırakabilir. 😊
Bu yazıda, Custom Hook’larda side effect’lerin (yan etkilerin) nasıl yönetilmesi gerektiğini, en yaygın hataları ve doğru pattern’leri inceleyeceğiz.
Hatırlatma: Side Effect Nedir?
“Side effect”, component’in render’ından bağımsız çalışan işlemdir. Yani React’in saf fonksiyon mantığını bozan her şey:- Veri çekmek (
fetch) - Event listener eklemek
- DOM veya storage manipülasyonu
- Timer veya subscription başlatmak
React’te bu işleri kontrol altına almak için useEffect, useLayoutEffect gibi hook’lar kullanılır.
Custom Hook yazarken bu hook’lar, dış dünyayla etkileşim kurmanın ana aracıdır.
Basit Bir Custom Hook Örneği Verelim;
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth)
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
return width
}
Bu hook pencere genişliğini izler.- Side effect:
addEventListener→ dış dünya ile etkileşim. - Cleanup:
removeEventListener→ sızıntıyı önler.
Yaygın Hata 1: Dependency Kaosu
Custom Hook içindeuseEffect kullanıyorsan, dependency listesi hayati önem taşır.useEffect(() => {
fetch(`/api/data?query=${query}`)
}, []) // ❌ query dependency’si eksik!
Bu durumda query değişse bile, fetch sadece bir kez çalışır.Ama eklersen:
useEffect(() => {
fetch(`/api/data?query=${query}`)
}, [query])
Artık her değişiklikte fetch yapılır.
💡 İpucu: ESLint’in react-hooks/exhaustive-deps kuralını asla kapatma. Bu kural, hayat kurtarır.
Yaygın Hata 2: Cleanup Eksikliği
Bir Custom Hook timer, subscription veya event listener başlatıyorsa, mutlaka cleanup dönmelidir.function useTimer() {
useEffect(() => {
const id = setInterval(() => console.log('tick'), 1000)
return () => clearInterval(id)
}, [])
}
Eğer clearInterval eklemezsen, component unmount olsa bile işlem devam eder. Sonuç: Hafıza sızıntısı (memory leak).💥Yaygın Hata 3: State ve Effect’in Ayrılmaması
Bazı geliştiriciler tekuseEffect içinde hem state hem side effect yönetir -- bu okunabilirliği bozar.useEffect(() => {
setData(fetchData()) // ❌ Hem state, hem effect
}, [])
Doğrusu:useEffect(() => {
fetchData().then(setData)
}, [])
Ya da daha temiz: Ayrı hook’lar.const data = useDataFetcher('/api/posts')
Bu yapı test edilebilirliği ve yeniden kullanılabilirliği artırır.Custom Hook’larda useEffect Kullanım Desenleri
1. Veri Çekme (Data Fetching)
function useFetch(url) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
let ignore = false
setLoading(true)
fetch(url)
.then(res => res.json())
.then(json => { if (!ignore) setData(json) })
.catch(setError)
.finally(() => setLoading(false))
return () => { ignore = true }
}, [url])
return { data, error, loading }
}
Bu pattern’de “ignore flag” ile unmount sonrası setState engellenir -- klasik React hatası: "Can't perform a React state update on an unmounted component." önlenir. ✅2. Event Listener Yönetimi
function useKeyPress(targetKey) {
const [pressed, setPressed] = useState(false)
useEffect(() => {
const down = (e) => e.key === targetKey && setPressed(true)
const up = (e) => e.key === targetKey && setPressed(false)
window.addEventListener('keydown', down)
window.addEventListener('keyup', up)
return () => {
window.removeEventListener('keydown', down)
window.removeEventListener('keyup', up)
}
}, [targetKey])
return pressed
}
Reaktif, güvenli ve temiz -- Hook dünyasında küçük bir sanat eseri. ✅Side Effect Yönetiminde En İyi Uygulamalar
- Cleanup’ı asla unutma. Her effect bir iz bırakır, sen silmezsen React silmez.
- Dependency dizisini doğru doldur. Her dependency, deterministik davranış sağlar.
- Async işlemleri iptal et. Fetch işlemlerinde AbortController kullanmak modern bir çözümdür.
- Effect’leri böl. Bir useEffect birden fazla işi yapmamalı.
- Custom Hook test edilebilir olmalı. DOM’a dokunmadan jest/react-testing-library ile kontrol edilebilmeli.
Reaktif Pattern: Effect + Memo Kombosu
function useFilteredList(list, query) {
const filtered = useMemo(() => list.filter(i => i.includes(query)), [list, query])
useEffect(() => {
console.log('Liste güncellendi')
}, [filtered])
return filtered
}
Burada useMemo hesaplamayı optimize eder, useEffect yalnızca sonuç değişince çalışır. Performans + doğruluk el ele. 🤝Debugging İpuçları
- React DevTools → Components sekmesinde Hook değerlerini izle.
- useEffect tetiklenme sayısını logla.
- Gereksiz render’ı tespit etmek için React Profiler kullan.
useEffect(() => {
console.count('Effect tetiklendi')
})
Bir sayfa 5 kez render oluyorsa, nedenini hemen anlarsın.Sonuç
Custom Hook’lar kodunuzu sadeleştirir amauseEffect yönetimi doğru yapılmazsa karmaşayı artırır.Kısaca:
- Side effect = render dışı işlem.
- Her effect’in bir cleanup’ı olmalı.
- Dependencies hayat kurtarır.
- Custom Hook’lar test edilebilir ve izole olmalı.