19.12.2025

React’te Custom Hook Tasarımı: UI’dan Bağımsız Mantık Katmanı Kurmak

Custom hook’larla tekrar eden iş mantığını UI’dan ayırın; test edilebilir, okunabilir React kodu yazın.

Neden custom hook?

React projeleri büyüdükçe asıl tekrar eden şey çoğu zaman UI değil, iş mantığıdır: form doğrulama, sorgu parametresi yönetimi, debounced arama, localStorage senkronu, websocket abonelikleri…

Custom hook yaklaşımıyla bu mantığı bileşenlerden ayırıp:

  • Aynı davranışı farklı UI’larda tekrar kullanır,
  • Daha küçük bileşenler yazarsınız,
  • Mantığı izole şekilde test etmek kolaylaşır.

Aşağıda “yeni bir açı”: UI’dan tamamen bağımsız, farklı ekranlarda çalışacak bir arama deneyimini küçük parçalar halinde kuralım.


Örnek 1: Debounced arama için useDebouncedValue

Kullanıcı yazarken her tuş vuruşunda istek atmak yerine değeri geciktirelim.

import { useEffect, useState } 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; }

Bu hook saf mantık: input, modal, header araması… nerede isterseniz kullanırsınız.


Örnek 2: URL ile senkron çalışan arama: useQueryParam

Arama teriminin URL’de durması (paylaşılabilir link, geri/ileri tuşları) iyi bir kullanıcı deneyimidir.

React Router v6 kullanan bir senaryo:

import { useSearchParams } from "react-router-dom";
import { useCallback } from "react";

export function useQueryParam(key) { const [params, setParams] = useSearchParams();

const value = params.get(key) ?? "";

const setValue = useCallback( (next) => { const copy = new URLSearchParams(params); if (!next) copy.delete(key); else copy.set(key, String(next)); setParams(copy, { replace: true }); }, [key, params, setParams] );

return [value, setValue]; }

Hook’ı kullanan bileşen URL detayını bilmek zorunda kalmaz.


Örnek 3: Hepsini birleştiren “arama modeli” hook’u

Şimdi UI’dan bağımsız bir “arama modeli” oluşturalım: input değeri URL ile senkron olsun, istekler debounced gitsin, iptal edilebilir olsun.

import { useEffect, useMemo, useState } from "react";
import { useDebouncedValue } from "./useDebouncedValue";
import { useQueryParam } from "./useQueryParam";

export function useProductSearch(fetcher) { const [q, setQ] = useQueryParam("q"); const debouncedQ = useDebouncedValue(q, 400);

const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null);

useEffect(() => { const ac = new AbortController();

async function run() {
  setLoading(true);
  setError(null);
  try {
    const result = await fetcher(debouncedQ, { signal: ac.signal });
    setData(result);
  } catch (e) {
    if (e.name !== "AbortError") setError(e);
  } finally {
    setLoading(false);
  }
}

run();
return () => ac.abort();

}, [debouncedQ, fetcher]);

return useMemo( () => ({ q, setQ, debouncedQ, data, loading, error }), [q, setQ, debouncedQ, data, loading, error] ); }

UI katmanı (örnek kullanım)

function ProductSearchPage({ api }) {
  const { q, setQ, data, loading, error } = useProductSearch(api.searchProducts);

return (

   setQ(e.target.value)}
    placeholder="Ürün ara…"
  />

  {loading && Yükleniyor…

} {error && Hata: {String(error)}

}

    {data.map((p) => (
      {p.name}

    ))}

); }

Burada önemli nokta: Sayfa sadece render eder. Arama akışının tüm karmaşıklığı hook içinde kalır.


İyi custom hook tasarımı için 4 kısa kural

  1. Tek sorumluluk: useDebouncedValue sadece debouncing yapıyor; URL işiyle karışmıyor.
  2. Bağımlılıkları dışarıdan al: fetcher fonksiyonunu parametre geçmek, test etmeyi kolaylaştırır.
  3. İptal/cleanup unutma: AbortController veya abonelik temizliği, “setState on unmounted” problemlerini azaltır.
  4. UI’yı zorlamama: Hook HTML bilmemeli; sadece veri/aksiyon döndürmeli.

Sonuç

Custom hook’lar, React’te “bileşen tekrar kullanımı”ndan farklı olarak mantık tekrarını hedefler. Debounce + URL senkronu + iptal edilebilir istek gibi parçaları bir araya getirerek daha temiz bir mimari kurabilirsiniz.