Bayangin 5 komponen di app kamu masing-masing pake pattern: useState buat data, useState buat loading, useEffect buat fetch — copy-paste kode yang sama 5x. Effort nge-update bug fix? 5 tempat. Buang ke library? Gak bisa — udah tercampur dengan UI. Solusi React: custom hook = ekstrak shared LOGIC ke fungsi yang dimulai dengan use, panggil dari komponen mana saja kayak hook bawaan. Reuse logika tanpa share UI. Pattern ini bikin codebase 10x lebih maintainable.
💡 Aturan emas: nama harus mulai dengan
use.useToggle,useFetch,useLocalStorage,useDebounce. Bukan cuma convention — Linter (eslint-plugin-react-hooks) check rule "Hooks must be called in same order tiap render", dan deteksinya berdasar prefixuse. Lupa prefix = aturan gak ke-enforce, bug subtle.
Custom Hook Pertama
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue)
const toggle = () => setValue(v => !v)
return [value, toggle]
}
// Pemakaian — clean banget
function App() {
const [isOpen, toggleOpen] = useToggle(false)
return (
<div>
<button onClick={toggleOpen}>{isOpen ? "Tutup" : "Buka"}</button>
{isOpen && <p>Konten tersembunyi!</p>}
</div>
)
}
function Modal() {
const [isVisible, toggleVisible] = useToggle() // reuse di komponen lain
// ...
}
Custom Hook untuk Fetch Data
function useFetch(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let cancelled = false
setLoading(true)
fetch(url)
.then(r => r.json())
.then(d => { if (!cancelled) setData(d) })
.catch(e => { if (!cancelled) setError(e.message) })
.finally(() => { if (!cancelled) setLoading(false) })
return () => { cancelled = true }
}, [url])
return { data, loading, error }
}
// Pemakaian — 1 baris instead of 20
function Users() {
const { data, loading, error } = useFetch("/api/users")
if (loading) return <Spinner />
if (error) return <Error message={error} />
return <UserList users={data} />
}
Custom Hook + LocalStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key)
return stored ? JSON.parse(stored) : initialValue
})
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value))
}, [key, value])
return [value, setValue]
}
// Pemakaian
function Settings() {
const [theme, setTheme] = useLocalStorage("theme", "light")
// theme persist across reload + tab close, automatically
}
Aturan Custom Hook
- Nama mulai dengan
use(useToggle, BUKANtoggle) - Hanya panggil hook di top level — gak boleh di if/loop/condition
- Hanya panggil dari React component atau hook lain — gak boleh dari util biasa
- Bisa panggil hook lain —
useState,useEffect, atau hook custom lain
🎭 Analogi sehari-hari
Custom hook itu kayak resep mix bumbu rahasia warung soto. Dulu tiap koki bikin sendiri (bawang putih, ketumbar, lengkuas, takarannya beda-beda) — rasanya gak konsisten, ngabisin waktu, kalo koki pindah ilmu ilang. Sekarang: bumbu mix dijadi paket yang siap pake (useMixedSpices()), tinggal taro di tiap masakan, rasa konsisten, koki lain bisa pake. Plus pas perlu ubah resep (bug fix), cukup ubah di paket bumbu — semua masakan otomatis pake versi baru. Custom hook sama: extract logic kompleks (state + effect + cleanup) jadi paket reusable, panggil di komponen mana pun.
⚠️ Jebakan yang sering ditemui
- Lupa prefix
use—myToggle()gak masuk lint rule, hooks bisa dipanggil di tempat salah tanpa warning. SELALUuseToggle. - Panggil hook di if/loop dalam custom hook — VIOLATES Rules of Hooks. Hook custom tetap kena aturan top-level.
- Bingung custom hook vs reusable function — kalau function pakai
useState/useEffectdi dalamnya, WAJIB jadi hook (mulaiuse). Kalau cuma logic murni (no state, no effect), function biasa. - Return signature inconsistent — kadang object
{ value, toggle }, kadang array[value, toggle]. Pilih satu pattern, konsisten. Modern React community prefer object (named, easier rename). - Custom hook nge-take dependencies tapi gak pass ke deps array —
useFetch(url)tapi useEffect deps[]= stale. Pass dependency ke deps internal. - Over-extract — extract logic 3 baris jadi custom hook = boilerplate ngalahin keuntungan. Extract pas pattern udah berulang 2-3x.
- Pakai custom hook di luar component — gak jalan, hook butuh React render cycle.
🎯 Bikin custom hook atau function biasa?
- Pakai useState/useEffect/hook lain di dalam → custom hook (
use...)- Logic murni (no state, no effect) → function biasa
- Cuma format/transform data → utility function
- Akses DOM, browser API stateful → biasanya custom hook
- Wrap untuk API library (Firebase, Supabase) → custom hook (manage subscription state)
- Logic share antar 2-3 komponen + ada state → custom hook
- Logic share antar 2-3 komponen + STATELESS → function biasa
Library populer custom hook: react-use, usehooks-ts — referensi pattern yang udah teruji.
TL;DR: Custom hook = ekstrak shared logic ke fungsi yang start dengan use, panggil dari komponen mana saja. Reuse stateful logic tanpa duplicate code. Wajib prefix use (lint rule). Tunduk Rules of Hooks (top-level call only). Common pattern: useToggle, useFetch, useLocalStorage, useDebounce. Return signature: object lebih clean dari array. Library populer: react-use, usehooks-ts. JANGAN over-extract — pattern muncul 2-3x baru extract.