Reactjs Custom Hooks State Sharing Patterns
Definisi
Custom hook adalah function JavaScript biasa yang diawali dengan use dan di dalamnya memakai hook React lain untuk mengekstrak logic yang bisa dipakai ulang. Tujuannya bukan membuat abstraksi yang keren-kerenan, tapi membuat komponen tetap tipis, fokus ke UI, dan mudah dirawat.
Di topik ini ada dua hal yang sering nyambung:
- Custom hook untuk memisahkan logic dari tampilan.
- State sharing patterns untuk membagi state ke komponen lain tanpa membuat komponen induk jadi terlalu berat.
Kalau disederhanakan, custom hook menjawab pertanyaan: “logic ini sebaiknya diletakkan di mana?”. Sedangkan state sharing patterns menjawab: “state ini harus tinggal di mana, dan siapa yang boleh ikut membaca atau mengubahnya?”
Kapan Membuat Custom Hook
Buat custom hook kalau logic yang sama mulai muncul berulang, tetapi UI-nya tetap berbeda.
Tanda-tandanya
- Ada pola fetch, loading, error, dan retry yang dipakai di banyak komponen.
- Ada event listener, timer, atau subscription yang polanya sama.
- Ada transformasi state yang cukup kompleks dan mulai mengotori body komponen.
- Ada kebutuhan sinkronisasi yang harus dikelola dengan rapi, misalnya localStorage, query string, atau state form.
- Komponen jadi panjang karena terlalu banyak detail non-UI.
Jangan buru-buru bikin custom hook kalau
- Logic-nya hanya dipakai sekali dan masih pendek.
- Nama abstraksinya belum jelas, jadi justru membingungkan.
- Yang dibutuhkan sebenarnya hanya helper function biasa, bukan hook.
- Komponen masih enak dibaca tanpa pemecahan tambahan.
Prinsip amannya begini: kalau logic itu punya lifecycle React atau bergantung pada hook lain, custom hook masuk akal. Kalau logic-nya murni transformasi data tanpa state atau effect, fungsi biasa sering lebih tepat.
Memisahkan Logic dari UI
Tujuan utama custom hook adalah memisahkan mekanisme dari representasi.
- UI bertugas menampilkan state dan menerima interaksi.
- Logic bertugas mengatur state, effect, handler, dan keputusan data.
Dengan begitu, komponen tidak perlu tahu detail cara data diambil, cara retry dijalankan, atau cara subscription dibersihkan. Komponen cukup menerima hasil akhir dari hook.
Pola yang sehat
import { useUsers } from './useUsers';
function UsersPage() {
const { users, loading, error, refetch } = useUsers();
if (loading) return <p>Memuat data...</p>;
if (error) return <button onClick={refetch}>Coba lagi</button>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Di contoh ini:
UsersPagefokus ke tampilan.useUsersfokus ke pengambilan data dan state lifecycle.- Komponen jadi lebih ringan karena detail async tidak bercampur dengan markup.
Alur pemisahan logic dan UI
Alur ini membantu menjaga satu aturan sederhana: komponen membaca hasil, hook mengurus proses.
Struktur Custom Hook yang Enak Dirawat
Custom hook yang bagus biasanya punya batas yang jelas.
Isi yang cocok masuk hook
useStateuntuk menyimpan state lokal.useEffectuntuk side effect.useMemodanuseCallbackkalau memang perlu stabilitas referensi atau hasil hitung.- handler yang berkaitan dengan satu domain perilaku.
- cleanup dari subscription, timer, atau listener.
Isi yang sebaiknya tetap di komponen
- markup JSX.
- layout visual.
- detail presentasi seperti className, label, dan susunan elemen.
- keputusan UI yang spesifik untuk satu halaman saja.
Bentuk output hook yang rapi
Biasanya hook mengembalikan object agar mudah dibaca saat destructuring:
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const open = () => setValue(true);
const close = () => setValue(false);
const toggle = () => setValue(prev => !prev);
return { value, open, close, toggle };
}Output seperti ini enak dipakai ulang karena consumer tidak perlu tahu implementasi internalnya.
State Sharing Patterns
Berbagi state itu bukan selalu berarti memakai global state. Dalam banyak kasus, masalahnya selesai dengan memindahkan state ke tempat yang paling tepat.
1. Keep state local
Kalau state cuma dipakai satu komponen, tetap lokal saja.
Contoh:
- input controlled
- toggle panel
- tab aktif pada satu kartu
- open/close modal yang hanya dipakai di satu konteks
Ini pilihan paling sederhana dan paling mudah di-debug.
2. Lift state up
Kalau dua atau lebih komponen butuh state yang sama, naikkan state ke parent terdekat yang relevan.
function SearchPanel() {
const [query, setQuery] = useState('');
return (
<>
<SearchInput value={query} onChange={setQuery} />
<SearchResult query={query} />
</>
);
}Pola ini cocok kalau:
- consumer state masih dekat secara struktur.
- parent memang logis jadi sumber kebenaran.
- alur data masih pendek dan jelas.
3. Use custom hook as state container
Kadang state sharing masih lokal, tapi logic-nya terlalu berat kalau ditaruh langsung di komponen parent. Di sini custom hook membantu.
function useSearchPanel() {
const [query, setQuery] = useState('');
const [filters, setFilters] = useState([]);
return {
query,
setQuery,
filters,
setFilters,
};
}
function SearchPanel() {
const search = useSearchPanel();
return (
<>
<SearchInput value={search.query} onChange={search.setQuery} />
<SearchFilters value={search.filters} onChange={search.setFilters} />
</>
);
}Ini membuat parent tetap ramping, sementara state logic terkumpul di satu tempat.
4. Context untuk state yang benar-benar lintas subtree
Kalau state perlu diakses banyak komponen di level berbeda, Context bisa jadi pilihan.
Gunakan Context kalau:
- data dipakai lintas banyak level komponen.
- nilai itu relevan untuk satu subtree aplikasi.
- prop drilling mulai mengganggu pembacaan kode.
Tapi tetap hati-hati: Context bukan alasan untuk memindahkan semua state ke atas. Kalau terlalu luas, satu perubahan kecil bisa memicu rerender yang tidak perlu.
5. Shared hook + Context pattern
Ini pola yang cukup sehat kalau kita ingin logic dan akses state sama-sama rapi.
- custom hook menyusun perilaku.
- Context menyediakan nilai ke consumer.
- komponen UI hanya mengonsumsi hasilnya.
Pola ini cocok untuk area aplikasi yang memang punya shared state, misalnya auth session, theme, cart, atau sidebar state.
Kapan Memilih Pola yang Mana
Pilih local state kalau
- state hanya dipakai satu komponen.
- tidak ada consumer lain.
- logic masih kecil.
Pilih lift state up kalau
- dua anak komponen perlu state yang sama.
- parent masih jadi tempat paling masuk akal untuk source of truth.
- tidak butuh provider atau store tambahan.
Pilih custom hook kalau
- logic dipakai ulang.
- komponen mulai terlalu penuh dengan effect dan handler.
- ingin memisahkan mekanisme dari tampilan.
Pilih Context kalau
- state dibaca banyak komponen di subtree yang berbeda.
- prop drilling mulai panjang.
- state itu memang terasa seperti kebutuhan area, bukan sekadar kebutuhan satu komponen.
Pilih store eksternal kalau
- state dibagi lintas banyak area dan mulai terasa seperti domain state.
- interaksi antarfitur makin kompleks.
- Context mulai berat atau terlalu sering rerender.
Kalau kebutuhan belum sampai sana, jangan lompat ke store terlalu cepat. Biasanya justru custom hook + lift state up sudah cukup.
Contoh Pola Praktis
Contoh 1: hook untuk fetch data
function useUsers() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const refetch = useCallback(async () => {
try {
setLoading(true);
setError(null);
const res = await fetch('/api/users');
const data = await res.json();
setUsers(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
refetch();
}, [refetch]);
return { users, loading, error, refetch };
}Poin pentingnya:
- fetch logic tidak mengotori UI.
- error handling tersentralisasi.
- komponen pemakai jadi lebih mudah diuji.
Contoh 2: hook untuk shared UI state
function useModalState() {
const [isOpen, setIsOpen] = useState(false);
const open = useCallback(() => setIsOpen(true), []);
const close = useCallback(() => setIsOpen(false), []);
const toggle = useCallback(() => setIsOpen(prev => !prev), []);
return { isOpen, open, close, toggle };
}Kalau modal dipakai bersama beberapa tombol dan header action, hook ini menjaga logika tetap konsisten.
Jebakan Umum
1. Custom hook yang terlalu besar
Kalau hook mulai berisi terlalu banyak tanggung jawab, pecah menjadi beberapa hook yang lebih spesifik.
Tanda-tandanya:
- satu hook mengurus fetch, filter, debounce, dan selection sekaligus.
- sulit memberi nama yang masuk akal.
- consumer harus tahu terlalu banyak detail internal.
2. Hook yang tidak benar-benar reusable
Kalau hanya dipakai satu komponen dan tidak mengurangi kompleksitas, mungkin hook itu belum perlu.
3. State sharing yang dipaksa ke Context
Context bukan tempat parkir semua state. Gunakan hanya kalau kebutuhan berbagi memang nyata.
4. UI dan logic tetap bercampur
Kalau komponen masih penuh dengan useEffect, fetch, setState, dan conditional logic, kemungkinan pemisahan belum selesai.
5. Abstraksi yang terlalu dini
Membuat custom hook sebelum pola reuse-nya jelas sering menghasilkan nama yang ragu-ragu dan API yang kurang pas.
Ringkasan Praktis
- Buat custom hook saat logic mulai berulang, berat, atau terkait lifecycle React.
- Pisahkan UI dan logic dengan aturan sederhana: komponen menampilkan hasil, hook mengurus proses.
- Mulai dari local state, lalu lift state up kalau perlu berbagi.
- Pakai Context hanya kalau sharing memang lintas subtree.
- Jangan naik ke store eksternal sebelum kebutuhan benar-benar jelas.
Referensi Terkait
- Hooks Dasar React.js:
useState,useEffect,useMemo,useCallback, danuseRef - Context, Composition, dan Prop Drilling di React.js
- Component, Props, State, dan Render Flow
- Forms React.js: Controlled vs Uncontrolled, Lifting State, Validation, dan Submit Flow