React Hooks Nedir? useEffect, Advanced Patterns ve En Önemli 5 Konu

Hamza İnce
2025-09-19
40 dk
0%

React Hooks, fonksiyonel bileşenleri güçlü, esnek ve test edilebilir hale getiren API setidir. Bu yazı, useEffect etrafında dönen pratiğe odaklanırken React ile ilgili kritik 5 konuyu derinlemesine ele alacak, örnekler, antipattern'ler, test stratejileri ve production-ready custom hook'lar sunacaktır.

İçindekiler

  • React Hooks'a Hızlı Giriş
  • useEffect: Temel Kavramlar ve Yürütme Zamanlaması
  • Dependency Array (Bağımlılık Dizisi) ve Kapanış (Closure) Problemleri
  • Cleanup (Temizlik) Mantığı — Neden Önemli?
  • Asenkron İşlemler, AbortController ve Yararlı Kalıplar
  • useLayoutEffect vs useEffect
  • Advanced Patterns: Custom Hooks Koleksiyonu
  • Performans: useMemo, useCallback, React.memo ile Etkili Optimizasyon
  • State Yönetimi Stratejileri
  • Testing: useEffect ve Custom Hooks Nasıl Test Edilir?
  • Migration: Class Components'ten Hooks'a Geçiş
  • Production Checklist & Best Practices
  • Örnekler: Gerçek Dünya Senaryoları
  • Sonuç ve Sonraki Adımlar

1. React Hooks'a Hızlı Giriş

React Hooks, fonksiyonel bileşenlerde state ve lifecycle benzeri özellikleri kullanmamızı sağlar. Önce en temel hook'ları hatırlayalım:

  • useState: Component içi state.
  • useEffect: Side-effect'leri çalıştırma (veri çekme, listener vs.).
  • useRef: Mutable değer saklama, DOM referansları.
  • useMemo & useCallback: Hesaplamaların ve fonksiyonların memoize edilmesi.
  • Custom Hooks: Tekrarlayan mantıkları paketleme.

Bu yazıda useEffect odağımız olduğu için diğer hook'ların nasıl useEffect ile beraber kullanılacağına bolca örnek vereceğim.


2. useEffect: Temel Kavramlar ve Yürütme Zamanlaması

useEffect, React bileşeni render edildikten sonra çalıştırılan bir fonksiyondur (commit sonrasında). Temel imza:

useEffect(() => {
    // effect
    return () => {
      // cleanup (opsiyonel)
    };
  }, [deps]);

Yürütme zamanlaması açısından:

  • Render tamamlandıktan sonra (paint sonrası) asenkron çalışır.
  • useLayoutEffect ise paint'ten önce senkron çalışır — ölçüm ve DOM manipülasyonları için kullanışlıdır.

Basit örnek

useEffect(() => {
    console.log('Component mounted or deps changed');
    return () => console.log('Cleanup on unmount or before next effect');
  }, [dep1, dep2]);

3. Dependency Array (Bağımlılık Dizisi) ve Closure Problemleri

useEffect'in ikinci argümanı dependency array'dir. Bu dizi içindeki değerlerden herhangi biri değiştiğinde effect tekrar çalışır. Buradaki en büyük tuzaklar closure kaynaklı stale değerlerdir.

Stale closure örneği

Şu kod yanlış sonuç verebilir:

function Counter() {
    const [count, setCount] = useState(0);
  
    useEffect(() => {
      const id = setInterval(() => {
        // count burada effect'in ilk render anındaki değeri kullanır
        setCount(count + 1);
      }, 1000);
  
      return () => clearInterval(id);
    }, []); // bağımlılık boş -> interval içerisindeki count sabit kalır
  }

Doğru yaklaşım:

useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1); // functional update kullanarak stale closure'ı önle
    }, 1000);
    return () => clearInterval(id);
  }, []);

ESLint & react-hooks kuralları

eslint-plugin-react-hooks size hangi bağımlılıkların eklenmesi gerektiğini söyler. Genellikle uyarıları dikkate alın; uyarıları susturmadan önce doğru çözümü uyguladığınızdan emin olun.


4. Cleanup (Temizlik) Mantığı — Neden Önemli?

Effect içerisinde açılan abonelikler, event listener'lar veya zamanlayıcılar mutlaka temizlenmelidir. Aksi takdirde memory leak ve beklenmeyen davranışlar ortaya çıkar.

useEffect(() => {
    const handler = (e) => console.log(e);
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, []);

Bazı durumlarda cleanup asenkron olabilir; ör. WebSocket kapatma promise döndürebilir. Cleanup fonksiyonu synchronously çağrılmalı fakat içinde asenkron operasyonlar çalıştırılabilir:

useEffect(() => {
    const ws = new WebSocket(url);
    ws.onopen = () => /* ... */;
    return () => {
      ws.close();
      // ya da ws.send('bye') -> asenkron davranış olabilir ama close() sync çağrılmalı
    };
  }, [url]);

5. Asenkron İşlemler, AbortController ve Yararlı Kalıplar

fetch/cancel senaryolarında AbortController kullanmak modern ve temiz bir yaklaşımdır.

useEffect(() => {
    const ctrl = new AbortController();
  
    async function load() {
      try {
        const res = await fetch('/api/data', { signal: ctrl.signal });
        const json = await res.json();
        setData(json);
      } catch (err) {
        if (err.name !== 'AbortError') setError(err);
      }
    }
  
    load();
  
    return () => ctrl.abort();
  }, [url]);

Bu pattern, komponent unmount olduğunda veya url değiştiğinde network isteğini iptal eder.


6. useLayoutEffect vs useEffect

useLayoutEffect DOM güncellemeleri yapılmadan önce (syncronously) çalışır. Bu, layout ölçümleri veya paint öncesi DOM manipülasyonları için gereklidir. Ancak yanlış kullanıldığında UI'nizin bloke olmasına sebep olabilir.

Kural:

  • Çoğu zaman useEffect kullanın.
  • Sadece ölçüm/offset/toplam genişlik gibi paint öncesi kesin değere ihtiyaç varsa useLayoutEffect kullanın.

7. Advanced Patterns: Custom Hooks Koleksiyonu

Custom hook'lar, tekrar eden mantıkları paketlemenizi sağlar. Aşağıda production-ready bazı örnekler var.

usePrevious — Önceki Değeri Saklayan Hook

import { useRef, useEffect } from 'react';
  
  export function usePrevious(value) {
    const ref = useRef();
    useEffect(() => {
      ref.current = value;
    }, [value]);
    return ref.current;
  }

Kullanım:

const prevCount = usePrevious(count);

useDebouncedValue — Debounce Edilmiş Değer

import { useState, useEffect } from 'react';
  
  export function useDebouncedValue(value, delay = 300) {
    const [debounced, setDebounced] = useState(value);
  
    useEffect(() => {
      const id = setTimeout(() => setDebounced(value), delay);
      return () => clearTimeout(id);
    }, [value, delay]);
  
    return debounced;
  }

useEventListener — Güvenli Event Listener

import { useEffect, useRef } from 'react';
  
  export function useEventListener(eventName, handler, element = window) {
    const savedHandler = useRef();
  
    useEffect(() => {
      savedHandler.current = handler;
    }, [handler]);
  
    useEffect(() => {
      if (!element?.addEventListener) return;
      const eventListener = (event) => savedHandler.current(event);
      element.addEventListener(eventName, eventListener);
      return () => element.removeEventListener(eventName, eventListener);
    }, [eventName, element]);
  }

useFetch — Daha Gelişmiş Versiyon

import { useState, useEffect, useRef } from 'react';
  
  export function useFetch(url, options) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(Boolean(url));
    const [error, setError] = useState(null);
    const abortRef = useRef();
  
    useEffect(() => {
      if (!url) return;
      abortRef.current = new AbortController();
  
      setLoading(true);
      setError(null);
  
      fetch(url, { ...options, signal: abortRef.current.signal })
        .then(res => {
          if (!res.ok) throw new Error('Network response not ok');
          return res.json();
        })
        .then(json => setData(json))
        .catch(err => {
          if (err.name !== 'AbortError') setError(err);
        })
        .finally(() => setLoading(false));
  
      return () => abortRef.current?.abort();
    }, [url, JSON.stringify(options)]); // options değişimi karmaşıksa serialize edilebilir
  
    return { data, loading, error };
  }

8. useEffect ile WebSocket / Socket.IO Örneği

import { useEffect, useRef, useState } from 'react';
  import io from 'socket.io-client';
  
  export function useSocket(url) {
    const socketRef = useRef(null);
    const [messages, setMessages] = useState([]);
  
    useEffect(() => {
      socketRef.current = io(url);
  
      socketRef.current.on('message', msg => {
        setMessages(prev => [...prev, msg]);
      });
  
      return () => {
        socketRef.current.disconnect();
      };
    }, [url]);
  
    const send = (msg) => socketRef.current?.emit('message', msg);
  
    return { messages, send };
  }

9. Performans: useMemo, useCallback, React.memo ile Etkili Optimizasyon

Re-render'ları azaltmak için memoization kullanabilirsiniz. Ancak kullanım maliyeti vardır — her kullanımdan önce profil yapmak gerekir.

  • useMemo: Ağır hesaplamaların tekrarını önler.
  • useCallback: Child component'lere referans olarak geçirilen fonksiyonların identity değişimini engeller.
  • React.memo: Component'in propsları değişmediği sürece yeniden render edilmesini engeller.
const expensive = useMemo(() => heavyCalc(data), [data]);
  const memoizedHandler = useCallback(() => setValue(v => v + 1), []);

Profil ile hangi hook'un yararlı olduğunu tespit etmek kritik: her yerde useMemo/useCallback kullanmak ters etki yapabilir.


10. State Yönetimi Stratejileri

Küçük uygulamalarda useState yeterli olabilir. Orta ve büyük ölçekli uygulamalar için şu yaklaşımlar popüler:

  • Context + useReducer (basit global state)
  • Redux / RTK (büyük uygulamalar)
  • Zustand / Recoil (daha hafif alternatifler)
  • Server-state için React Query / SWR

Öneri: UI state (modal visibility, form inputs) için local state; veri getirme ve cache için React Query; global iş mantığı için minimal bir state store tercih edin.


11. Testing: useEffect ve Custom Hooks Nasıl Test Edilir?

Testing Library + Jest ile hook'ları test etmek için @testing-library/react-hooks veya renderHook kullanabilirsiniz. Ayrıca efektlerin DOM side-effect'lerini jest fake timers ve mock fetch ile test edin.

import { renderHook, act } from '@testing-library/react-hooks';
  import { useFetch } from './useFetch';
  
  test('useFetch başarıyla veri döner', async () => {
    global.fetch = jest.fn(() => Promise.resolve({
      ok: true,
      json: () => Promise.resolve({ name: 'ok' })
    }));
  
    const { result, waitForNextUpdate } = renderHook(() => useFetch('/api/test'));
  
    expect(result.current.loading).toBe(true);
    await waitForNextUpdate();
    expect(result.current.data).toEqual({ name: 'ok' });
  });

12. Migration: Class Components'ten Hooks'a Geçiş

Geçiş adımları özetle:

  1. Önce küçük, izole bileşenlerde deneyin.
  2. setState kullanımını useState veya useReducer ile değiştirin.
  3. Lifecycle metotlarını (componentDidMount, componentDidUpdate, componentWillUnmount) useEffect ile eşleştirin.
  4. Context tüketimini useContext ile optimize edin.

Örnek: componentDidMount'da fetch yapan sınıf bileşeni:

class OldComp extends React.Component {
    state = { data: null };
    componentDidMount() {
      fetch('/api').then(r => r.json()).then(data => this.setState({ data }));
    }
    render() { return <div>{this.state.data?.name}</div> }
  }

Hooks ile:

function NewComp() {
    const { data } = useFetch('/api');
    return <div>{data?.name}</div>;
  }

13. Common Antipatterns ve Nasıl Düzeltilir

  • Infinite loop: useEffect içinde bağımlılık dizisindeki değerleri setState ile güncellemek. Çözüm: Effect'i doğru bağımlılıklar ile sınırlayın ve gerekirse koşullandırın.
  • Excessive effects: Çok sayıda effect yerine mantığı birleştirin veya custom hook'a taşıyın.
  • Derin nesneleri deps'e koymak: Her render'da değişen referanslar yüzünden effect sürekli tetiklenir. Çözüm: useMemo ile nesne/fonksiyon referanslarını sabitleyin veya deps'i serialize edin (dikkatle).
  • Ignoring ESLint warnings: React Hooks lint uyarılarını kapatmayın; önce doğru çözümü uygulayın.

14. Production Checklist & Best Practices

  • Kullanılmayan effect'leri temizleyin.
  • Network istekleri için timeout/abort kullanımını kesinleştirin.
  • Event listener ve intervals için cleanup ekleyin.
  • useMemo/useCallback kullanmadan önce profiling yapın.
  • Custom hook API'lerini minimal ve declarative yapın.
  • Testing: kritik hook'lar için unit test yazın.
  • Accessibility: focus yönetimi, keyboard event'leri vs. effect'ler ile uyumlu olsun.

15. Örnekler: Gerçek Dünya Senaryoları

1) Infinite Scroll (useInfiniteScroll)

import { useState, useEffect, useRef } from 'react';
  
  export function useInfiniteScroll(fetchMore) {
    const [loading, setLoading] = useState(false);
    const sentinelRef = useRef();
  
    useEffect(() => {
      const el = sentinelRef.current;
      if (!el) return;
      const obs = new IntersectionObserver(entries => {
        if (entries[0].isIntersecting && !loading) {
          setLoading(true);
          fetchMore().finally(() => setLoading(false));
        }
      }, { rootMargin: '200px' });
  
      obs.observe(el);
      return () => obs.disconnect();
    }, [fetchMore, loading]);
  
    return { sentinelRef, loading };
  }

2) useAuth — Authentication State Yönetimi

import { useState, useEffect } from 'react';
  
  export function useAuth(authService) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);
  
    useEffect(() => {
      let mounted = true;
      authService.getCurrentUser().then(u => {
        if (mounted) setUser(u);
      }).finally(() => mounted && setLoading(false));
  
      const unsub = authService.onAuthStateChanged(u => setUser(u));
      return () => {
        mounted = false;
        unsub();
      };
    }, [authService]);
  
    return { user, loading };
  }

16. Debugging: useEffect Davranışlarını Nasıl İncelersiniz?

  • React DevTools Profiler ile hangi component'in ne zaman render olduğunu gözlemleyin.
  • console.log ile effect'lerin mount/unmount zamanlarını kaydedin (development).
  • ESLint exhaustive-deps uyarılarını dikkate alın.
  • Network tab ile isteğin iptal edilip edilmediğini kontrol edin.

17. İleri Seviye: useEffect + RxJS Kombinasyonu

import { useEffect, useState } from 'react';
  import { fromEvent } from 'rxjs';
  import { map, debounceTime } from 'rxjs/operators';
  
  function Search() {
    const [term, setTerm] = useState('');
  
    useEffect(() => {
      const sub = fromEvent(document.getElementById('q'), 'input')
        .pipe(
          map(e => e.target.value),
          debounceTime(300)
        )
        .subscribe(v => setTerm(v));
  
      return () => sub.unsubscribe();
    }, []);
  
    return <input id="q" />;
  }

18. useEffect ile Form Yönetimi Örneği (Debounce + Validation)

import { useState, useEffect } from 'react';
  import { useDebouncedValue } from './useDebouncedValue';
  
  function SearchBox({ onSearch }) {
    const [q, setQ] = useState('');
    const debounced = useDebouncedValue(q, 400);
  
    useEffect(() => {
      if (debounced.trim()) onSearch(debounced);
    }, [debounced, onSearch]);
  
    return <input value={q} onChange={e => setQ(e.target.value)} />;
  }

19. Hook Tasarım İlkeleri

  1. Hook'ları isimlendirirken use ile başlayın (örn. useFetch, useAuth).
  2. Hook'lar side-effect'leri minimal tutmalı; API declarative olmalı.
  3. Karmaşık logic'i küçük, test edilebilir parçalara bölün.
  4. Hook'ları birden fazla component arasında paylaştıracaksanız backward compatibility düşünün.

20. Kaynakça & Öğrenmeye Devam

  • Resmi React Hooks dokümantasyonu (reactjs.org)
  • eslint-plugin-react-hooks kuralları
  • Testing Library ve @testing-library/react-hooks
  • React DevTools Profiler
  • React Query / SWR (server state)

21. Özet ve Tavsiyeler

React Hooks modern frontend geliştirme için vazgeçilmezdir. useEffect, doğru kullandığınızda güçlü bir araçtır; yanlış kullanıldığında ise bug'ların ve performans sorunlarının kaynağı olur. Aşağıdaki kısa özet faydalı olacaktır:

  • useEffect side-effect yönetimi içindir. Cleanup döndürmeyi unutmayın.
  • Dependency array'leri doğru yazın; ESLint uyarılarını dikkate alın.
  • useMemo/useCallback yalnızca gerektiğinde; profil yapmadan yaygın kullanımdan kaçının.
  • Custom hook'ları küçük, deklaratif ve test edilebilir tutun.
  • Network isteklerinde AbortController kullanarak temiz iptal mekanizmaları kurun.

22. Bonus: Hızlı Referans Kartı (Cheat Sheet)

  • componentDidMount: useEffect(..., [])
  • componentWillUnmount: return () => { ... } inside useEffect
  • componentDidUpdate: useEffect(..., [deps])
  • Prev props/state: usePrevious
  • Debounce value: useDebouncedValue
  • Event listener: useEventListener
  • Abort fetch: AbortController + cleanup
Etiketler:
reacthooksuseEffectuseStatecustom-hooksperformancefrontendjavascript