Bir React custom hook, bileşenlerinizde tekrar eden mantığı tek bir yeniden kullanılabilir fonksiyona taşımanın resmî yoludur. Aynı useState + useEffect kombinasyonunu üç ayrı bileşene kopyaladığınızı fark ettiyseniz, bu kodu bir hook'a çıkarmanın tam zamanı. Bu rehberde custom hook'un ne olduğunu, nasıl yazıldığını ve hangi durumlarda gerçekten işe yaradığını gerçek örneklerle anlatacağım.
Custom hook nedir, ne değildir?
Custom hook, adı use ile başlayan ve içinde başka hook'lar (useState, useEffect, useRef, hatta başka custom hook'lar) çağırabilen sıradan bir JavaScript fonksiyonudur. Sihirli bir özelliği yoktur; React, use önekini görünce o fonksiyonun hook kurallarına uymasını bekler.
- Mantığı paylaşır, state'i paylaşmaz. İki bileşen aynı hook'u kullanırsa her biri kendi bağımsız state'ine sahip olur.
- JSX döndürmez; veri, fonksiyon veya bunların kombinasyonunu döndürür.
- Bir bileşen değildir; render etmez, sadece bir bileşenin (veya başka bir hook'un) içinden çağrılır.
En önemli ayrım şudur: bir bileşeni neden böldüğünüze "UI'ı tekrar kullanmak" diye cevap veriyorsanız bileşen çıkarın; "mantığı tekrar kullanmak" diye cevap veriyorsanız hook çıkarın.
İki kural: nerede çağrılır
Hook'lar, custom olsun olmasın, iki kurala tabidir ve bunları çiğnerseniz uygulama beklenmedik şekilde davranır:
- Sadece en üst seviyede çağırın. Döngü, koşul veya iç içe fonksiyon içinde hook çağırmayın. Böylece React, her render'da hook'ların aynı sırada çalıştığından emin olur.
- Sadece React fonksiyonlarından çağırın. Yani bileşenlerden veya başka custom hook'lardan. Sıradan bir yardımcı fonksiyondan hook çağırmayın.
Bu kuralları otomatik denetlemek için eslint-plugin-react-hooks paketini kurun; yanlış kullanımları daha yazarken yakalar.
İlk custom hook'umuz: useToggle
Basit ama günlük hayatta sürekli ihtiyaç duyulan bir örnekle başlayalım. Açılır menü, modal, "daha fazla göster" gibi her yerde bir boolean'ı tersine çevirmeniz gerekir:
import { useState, useCallback } from 'react';
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue(v => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return { value, toggle, setTrue, setFalse };
}
Kullanımı bir bileşende son derece temizdir:
function Sidebar() {
const { value: isOpen, toggle, setFalse } = useToggle();
return (
<>
<button onClick={toggle}>Menü</button>
{isOpen && <nav onMouseLeave={setFalse}>...</nav>}
</>
);
}
useCallback kullanmamızın sebebi, döndürülen fonksiyonların her render'da yeni referans olarak üretilmesini engellemektir; böylece bu fonksiyonları useEffect bağımlılığı olarak güvenle kullanabilirsiniz.
Yan etki içeren bir hook: useLocalStorage
Asıl güç, useEffect ve tarayıcı API'leri devreye girince ortaya çıkar. Bir değeri localStorage ile senkron tutan bir hook, onlarca bileşende tekrar edilecek mantığı tek yere toplar:
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const stored = window.localStorage.getItem(key);
return stored !== null ? JSON.parse(stored) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch {
// kota dolu veya gizli mod: sessizce yok say
}
}, [key, value]);
return [value, setValue];
}
Dikkat edilecek iki nokta var. Birincisi, useState'e bir fonksiyon verdik (lazy initializer); böylece localStorage okuması her render'da değil, yalnızca ilk render'da çalışır. İkincisi, JSON.parse ve localStorage erişimini try/catch ile sardık çünkü bozuk veri veya gizli sekme erişimi gerçek dünyada başa bela olur.
Veri çeken bir hook ve temizleme
Asenkron veri çekiminde en sık yapılan hata, bileşen kaldırıldıktan (unmount) sonra state güncellemeye çalışmaktır. AbortController ile bu temiz bir şekilde çözülür:
import { useState, useEffect } from 'react';
function useFetch(url) {
const [state, setState] = useState({ data: null, error: null, loading: true });
useEffect(() => {
const controller = new AbortController();
setState({ data: null, error: null, loading: true });
fetch(url, { signal: controller.signal })
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => setState({ data, error: null, loading: false }))
.catch(error => {
if (error.name !== 'AbortError') {
setState({ data: null, error, loading: false });
}
});
return () => controller.abort();
}, [url]);
return state;
}
Effect'ten döndürülen temizleme fonksiyonu, url değiştiğinde veya bileşen kaldırıldığında önceki isteği iptal eder. Böylece "Can't perform a React state update on an unmounted component" türü uyarıların ve yarış koşullarının önüne geçersiniz.
Ne zaman hook çıkarmalı, ne zaman beklemeli?
Erken soyutlama gerçek bir tuzaktır. Pratik bir kural seti:
- Aynı mantığı en az iki yerde kullanıyorsanız (veya tekrar edeceğinizden eminseniz) hook'a çıkarın.
- Bir bileşen içindeki effect ve state mantığı tek başına okunabilirliği bozacak kadar uzunsa, sadece test edilebilirlik için bile çıkarmak değer.
- Sade veri çekimi için
useFetchyazmak öğreticidir; ancak production'da önbellek, yeniden deneme ve geçersiz kılma istiyorsanız TanStack Query gibi olgun bir kütüphane çoğu zaman daha doğru tercihtir. - Hook'unuzun döndürdüğü değerleri net bir sözleşme gibi tasarlayın: dizi mi (konumsal,
useStategibi) yoksa nesne mi (isimli, çok alanlıysa) döndüreceğinize bilinçli karar verin.
Sık Sorulan Sorular
Custom hook ile normal fonksiyon arasındaki fark nedir?
Custom hook içinde useState, useEffect gibi diğer hook'ları çağırabilir ve React'in render yaşam döngüsüne bağlanır; normal yardımcı fonksiyon bunu yapamaz. Ayrım pratikte use önekiyle belirtilir ve linter bu öneke göre kural denetimi yapar.
İki bileşen aynı hook'u kullanırsa state'i paylaşır mı?
Hayır. Hook yalnızca mantığı paylaşır; her bileşen kendi izole state'ine sahip olur. State'i gerçekten paylaşmak istiyorsanız Context veya bir state yönetim çözümü kullanmalısınız.
Custom hook'ları nasıl test ederim?
React Testing Library'nin renderHook yardımcısıyla hook'u izole biçimde render edip döndürdüğü değerleri ve act içinde tetiklenen güncellemeleri doğrulayabilirsiniz. Hook saf mantık içeriyorsa onu ayrı bir fonksiyona bölüp doğrudan da test edebilirsiniz.
Tekrar eden React mantığınızı temiz hook'lara taşımak mı istiyorsunuz? Frontend mimarisi ve React projeleriniz için birlikte çalışalım — benimle iletişime geçin.