Pelajari cara menyusun custom hook React secara efektif untuk mengabstraksi logika kompleks, meningkatkan penggunaan kembali kode, dan menyempurnakan pemeliharaan dalam proyek Anda. Termasuk contoh praktis dan praktik terbaik.
Komposisi Custom Hook React: Menguasai Abstraksi Logika Kompleks
Custom hook React adalah alat yang ampuh untuk mengenkapsulasi dan menggunakan kembali logika stateful dalam aplikasi React Anda. Namun, seiring dengan meningkatnya kompleksitas aplikasi Anda, begitu pula logika di dalam custom hook Anda. Hal ini dapat menyebabkan hook monolitik yang sulit dipahami, diuji, dan dipelihara. Komposisi custom hook memberikan solusi untuk masalah ini dengan memungkinkan Anda memecah logika kompleks menjadi hook yang lebih kecil, lebih mudah dikelola, dan dapat digunakan kembali.
Apa itu Komposisi Custom Hook?
Komposisi custom hook adalah praktik menggabungkan beberapa custom hook yang lebih kecil untuk menciptakan fungsionalitas yang lebih kompleks. Alih-alih membuat satu hook besar yang menangani semuanya, Anda membuat beberapa hook yang lebih kecil, masing-masing bertanggung jawab atas aspek logika tertentu. Hook-hook yang lebih kecil ini kemudian dapat disusun bersama untuk mencapai fungsionalitas yang diinginkan.
Anggap saja seperti membangun dengan balok LEGO. Setiap balok (hook kecil) memiliki fungsi spesifik, dan Anda menggabungkannya dengan berbagai cara untuk membangun struktur yang kompleks (fitur yang lebih besar).
Manfaat Komposisi Custom Hook
- Peningkatan Penggunaan Kembali Kode: Hook yang lebih kecil dan lebih terfokus secara inheren lebih dapat digunakan kembali di berbagai komponen dan bahkan proyek yang berbeda.
- Pemeliharaan yang Ditingkatkan: Memecah logika kompleks menjadi unit-unit yang lebih kecil dan mandiri membuatnya lebih mudah untuk dipahami, di-debug, dan dimodifikasi. Perubahan pada satu hook cenderung tidak mempengaruhi bagian lain dari aplikasi Anda.
- Peningkatan Kemampuan Uji: Hook yang lebih kecil lebih mudah diuji secara terpisah, menghasilkan kode yang lebih kuat dan andal.
- Organisasi Kode yang Lebih Baik: Komposisi mendorong basis kode yang lebih modular dan terorganisir, membuatnya lebih mudah untuk dinavigasi dan memahami hubungan antara berbagai bagian aplikasi Anda.
- Pengurangan Duplikasi Kode: Dengan mengekstrak logika umum ke dalam hook yang dapat digunakan kembali, Anda meminimalkan duplikasi kode, yang mengarah ke basis kode yang lebih ringkas dan mudah dipelihara.
Kapan Menggunakan Komposisi Custom Hook
Anda harus mempertimbangkan untuk menggunakan komposisi custom hook ketika:
- Satu custom hook menjadi terlalu besar dan kompleks.
- Anda mendapati diri Anda menduplikasi logika serupa di beberapa custom hook atau komponen.
- Anda ingin meningkatkan kemampuan uji dari custom hook Anda.
- Anda ingin membuat basis kode yang lebih modular dan dapat digunakan kembali.
Prinsip Dasar Komposisi Custom Hook
Berikut adalah beberapa prinsip utama untuk memandu pendekatan Anda terhadap komposisi custom hook:
- Prinsip Tanggung Jawab Tunggal: Setiap custom hook harus memiliki satu tanggung jawab yang terdefinisi dengan baik. Ini membuatnya lebih mudah untuk dipahami, diuji, dan digunakan kembali.
- Pemisahan Kepentingan: Pisahkan berbagai aspek logika Anda ke dalam hook yang berbeda. Misalnya, Anda mungkin memiliki satu hook untuk mengambil data, yang lain untuk mengelola state, dan yang lain untuk menangani efek samping.
- Komposabilitas: Rancang hook Anda sehingga dapat dengan mudah disusun dengan hook lain. Ini sering melibatkan pengembalian data atau fungsi yang dapat digunakan oleh hook lain.
- Konvensi Penamaan: Gunakan nama yang jelas dan deskriptif untuk hook Anda untuk menunjukkan tujuan dan fungsionalitasnya. Konvensi umum adalah mengawali nama hook dengan `use`.
Pola Komposisi Umum
Beberapa pola dapat digunakan untuk menyusun custom hook. Berikut adalah beberapa yang paling umum:
1. Komposisi Hook Sederhana
Ini adalah bentuk komposisi yang paling dasar, di mana satu hook hanya memanggil hook lain dan menggunakan nilai kembaliannya.
Contoh: Bayangkan Anda memiliki hook untuk mengambil data pengguna dan satu lagi untuk memformat tanggal. Anda dapat menyusun hook-hook ini untuk membuat hook baru yang mengambil data pengguna dan memformat tanggal registrasi pengguna.
import { useState, useEffect } from 'react';
function useUserData(userId) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const jsonData = await response.json();
setData(jsonData);
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
fetchData();
}, [userId]);
return { data, loading, error };
}
function useFormattedDate(dateString) {
try {
const date = new Date(dateString);
const formattedDate = date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' });
return formattedDate;
} catch (error) {
console.error("Error formatting date:", error);
return "Invalid Date";
}
}
function useUserWithFormattedDate(userId) {
const { data, loading, error } = useUserData(userId);
const formattedRegistrationDate = data ? useFormattedDate(data.registrationDate) : null;
return { ...data, formattedRegistrationDate, loading, error };
}
export default useUserWithFormattedDate;
Penjelasan:
useUserDatamengambil data pengguna dari API.useFormattedDatememformat string tanggal menjadi format yang mudah dibaca pengguna. Ini menangani potensi kesalahan penguraian tanggal dengan baik. Argumen `undefined` pada `toLocaleDateString` menggunakan lokal pengguna untuk pemformatan.useUserWithFormattedDatemenyusun kedua hook. Hook ini pertama-tama menggunakanuseUserDatauntuk mengambil data pengguna. Kemudian, jika data tersedia, ia menggunakanuseFormattedDateuntuk memformatregistrationDate. Akhirnya, ia mengembalikan data pengguna asli bersama dengan tanggal yang diformat, status pemuatan, dan potensi kesalahan apa pun.
2. Komposisi Hook dengan State Bersama
Dalam pola ini, beberapa hook berbagi dan memodifikasi state yang sama. Hal ini dapat dicapai dengan menggunakan useContext atau dengan meneruskan state dan fungsi setter di antara hook.
Contoh: Bayangkan membangun formulir multi-langkah. Setiap langkah dapat memiliki hook sendiri untuk mengelola bidang input spesifik dan logika validasi langkah tersebut, tetapi semuanya berbagi state formulir umum yang dikelola oleh hook induk menggunakan useReducer dan useContext.
import React, { createContext, useContext, useReducer } from 'react';
// Define the initial state
const initialState = {
step: 1,
name: '',
email: '',
address: ''
};
// Define the actions
const ACTIONS = {
NEXT_STEP: 'NEXT_STEP',
PREVIOUS_STEP: 'PREVIOUS_STEP',
UPDATE_FIELD: 'UPDATE_FIELD'
};
// Create the reducer
function formReducer(state, action) {
switch (action.type) {
case ACTIONS.NEXT_STEP:
return { ...state, step: state.step + 1 };
case ACTIONS.PREVIOUS_STEP:
return { ...state, step: state.step - 1 };
case ACTIONS.UPDATE_FIELD:
return { ...state, [action.payload.field]: action.payload.value };
default:
return state;
}
}
// Create the context
const FormContext = createContext();
// Create a provider component
function FormProvider({ children }) {
const [state, dispatch] = useReducer(formReducer, initialState);
const value = {
state,
dispatch,
nextStep: () => dispatch({ type: ACTIONS.NEXT_STEP }),
previousStep: () => dispatch({ type: ACTIONS.PREVIOUS_STEP }),
updateField: (field, value) => dispatch({ type: ACTIONS.UPDATE_FIELD, payload: { field, value } })
};
return (
{children}
);
}
// Custom hook for accessing the form context
function useFormContext() {
const context = useContext(FormContext);
if (!context) {
throw new Error('useFormContext must be used within a FormProvider');
}
return context;
}
// Custom hook for Step 1
function useStep1() {
const { state, updateField } = useFormContext();
const updateName = (value) => updateField('name', value);
return {
name: state.name,
updateName
};
}
// Custom hook for Step 2
function useStep2() {
const { state, updateField } = useFormContext();
const updateEmail = (value) => updateField('email', value);
return {
email: state.email,
updateEmail
};
}
// Custom hook for Step 3
function useStep3() {
const { state, updateField } = useFormContext();
const updateAddress = (value) => updateField('address', value);
return {
address: state.address,
updateAddress
};
}
export { FormProvider, useFormContext, useStep1, useStep2, useStep3 };
Penjelasan:
- Sebuah
FormContextdibuat menggunakancreateContextuntuk menampung state formulir dan fungsi dispatch. - Sebuah
formReducermengelola pembaruan state formulir menggunakanuseReducer. Aksi sepertiNEXT_STEP,PREVIOUS_STEP, danUPDATE_FIELDdidefinisikan untuk memodifikasi state. - Komponen
FormProvidermenyediakan konteks formulir untuk anak-anaknya, membuat state dan dispatch tersedia untuk semua langkah formulir. Komponen ini juga mengekspos fungsi pembantu untuk `nextStep`, `previousStep`, dan `updateField` untuk menyederhanakan pengiriman aksi. - Hook
useFormContextmemungkinkan komponen untuk mengakses nilai konteks formulir. - Setiap langkah (
useStep1,useStep2,useStep3) membuat hook sendiri untuk mengelola input yang terkait dengan langkahnya dan menggunakanuseFormContextuntuk mendapatkan state dan fungsi dispatch untuk memperbaruinya. Setiap langkah hanya mengekspos data dan fungsi yang relevan dengan langkah itu, mematuhi prinsip tanggung jawab tunggal.
3. Komposisi Hook dengan Manajemen Lifecycle
Pola ini melibatkan hook yang mengelola berbagai fase dari siklus hidup komponen, seperti mounting, updating, dan unmounting. Hal ini sering dicapai dengan menggunakan useEffect di dalam hook yang disusun.
Contoh: Pertimbangkan komponen yang perlu melacak status online/offline dan juga perlu melakukan beberapa pembersihan saat unmount. Anda dapat membuat hook terpisah untuk setiap tugas ini dan kemudian menyusunnya.
import { useState, useEffect } from 'react';
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
return () => {
document.title = 'Original Title'; // Revert to a default title on unmount
};
}, [title]);
}
function useAppLifecycle(title) {
const isOnline = useOnlineStatus();
useDocumentTitle(title);
return isOnline; // Return the online status
}
export { useAppLifecycle, useOnlineStatus, useDocumentTitle };
Penjelasan:
useOnlineStatusmelacak status online pengguna menggunakan eventonlinedanoffline. HookuseEffectmenyiapkan event listener saat komponen di-mount dan membersihkannya saat di-unmount.useDocumentTitlememperbarui judul dokumen. Hook ini juga mengembalikan judul ke nilai default saat komponen di-unmount, memastikan tidak ada masalah judul yang tertinggal.useAppLifecyclemenyusun kedua hook. Hook ini menggunakanuseOnlineStatusuntuk menentukan apakah pengguna sedang online danuseDocumentTitleuntuk mengatur judul dokumen. Hook gabungan ini mengembalikan status online.
Contoh Praktis dan Kasus Penggunaan
1. Internasionalisasi (i18n)
Mengelola terjemahan dan peralihan lokal bisa menjadi rumit. Anda dapat menggunakan komposisi hook untuk memisahkan kepentingan:
useLocale(): Mengelola lokal saat ini.useTranslations(): Mengambil dan menyediakan terjemahan untuk lokal saat ini.useTranslate(key): Hook yang mengambil kunci terjemahan dan mengembalikan string yang diterjemahkan, menggunakan hookuseTranslationsuntuk mengakses terjemahan.
Ini memungkinkan Anda untuk dengan mudah beralih lokal dan mengakses terjemahan di seluruh aplikasi Anda. Pertimbangkan untuk menggunakan pustaka seperti i18next bersama dengan custom hook untuk mengelola logika terjemahan. Misalnya, useTranslations dapat memuat terjemahan berdasarkan lokal yang dipilih dari file JSON dalam berbagai bahasa.
2. Validasi Formulir
Formulir yang kompleks seringkali memerlukan validasi yang ekstensif. Anda dapat menggunakan komposisi hook untuk membuat logika validasi yang dapat digunakan kembali:
useInput(initialValue): Mengelola state dari satu bidang input.useValidator(value, rules): Memvalidasi satu bidang input berdasarkan serangkaian aturan (misalnya, wajib, email, minLength).useForm(fields): Mengelola state dan validasi seluruh formulir, menyusunuseInputdanuseValidatoruntuk setiap bidang.
Pendekatan ini mendorong penggunaan kembali kode dan membuatnya lebih mudah untuk menambah atau memodifikasi aturan validasi. Pustaka seperti Formik atau React Hook Form menyediakan solusi siap pakai tetapi dapat ditambah dengan custom hook untuk kebutuhan validasi spesifik.
3. Pengambilan dan Caching Data
Mengelola pengambilan data, caching, dan penanganan kesalahan dapat disederhanakan dengan komposisi hook:
useFetch(url): Mengambil data dari URL yang diberikan.useCache(key, fetchFunction): Melakukan cache hasil dari fungsi pengambilan menggunakan sebuah kunci.useData(url, options): MenggabungkanuseFetchdanuseCacheuntuk mengambil data dan melakukan cache pada hasilnya.
Ini memungkinkan Anda untuk dengan mudah melakukan cache pada data yang sering diakses dan meningkatkan kinerja. Pustaka seperti SWR (Stale-While-Revalidate) dan React Query menyediakan solusi pengambilan data dan caching yang kuat yang dapat diperluas dengan custom hook.
4. Autentikasi
Menangani logika autentikasi bisa jadi rumit, terutama ketika berhadapan dengan metode autentikasi yang berbeda (misalnya, JWT, OAuth). Komposisi hook dapat membantu memisahkan berbagai aspek dari proses autentikasi:
useAuthToken(): Mengelola token autentikasi (misalnya, menyimpan dan mengambilnya dari local storage).useUser(): Mengambil dan menyediakan informasi pengguna saat ini berdasarkan token autentikasi.useAuth(): Menyediakan fungsi terkait autentikasi seperti login, logout, dan signup, dengan menyusun hook lainnya.
Pendekatan ini memungkinkan Anda untuk dengan mudah beralih antara metode autentikasi yang berbeda atau menambahkan fitur baru ke proses autentikasi. Pustaka seperti Auth0 dan Firebase Authentication dapat digunakan sebagai backend untuk mengelola akun pengguna dan autentikasi, dan custom hook dapat dibuat untuk berinteraksi dengan layanan-layanan ini.
Praktik Terbaik untuk Komposisi Custom Hook
- Jaga Agar Hook Tetap Terfokus: Setiap hook harus memiliki tujuan yang jelas dan spesifik.
- Hindari Nesting yang Dalam: Batasi jumlah level komposisi untuk menghindari membuat kode Anda sulit dipahami. Jika sebuah hook menjadi terlalu kompleks, pertimbangkan untuk memecahnya lebih lanjut.
- Dokumentasikan Hook Anda: Berikan dokumentasi yang jelas dan ringkas untuk setiap hook, menjelaskan tujuan, input, dan outputnya. Ini sangat penting untuk hook yang digunakan oleh pengembang lain.
- Uji Hook Anda: Tulis unit test untuk setiap hook untuk memastikan bahwa hook tersebut berfungsi dengan benar. Ini sangat penting untuk hook yang mengelola state atau melakukan efek samping.
- Pertimbangkan Menggunakan Pustaka Manajemen State: Untuk skenario manajemen state yang kompleks, pertimbangkan untuk menggunakan pustaka seperti Redux, Zustand, atau Jotai. Pustaka-pustaka ini menyediakan fitur yang lebih canggih untuk mengelola state dan dapat menyederhanakan komposisi hook.
- Pikirkan Tentang Penanganan Kesalahan: Terapkan penanganan kesalahan yang kuat di dalam hook Anda untuk mencegah perilaku yang tidak terduga. Pertimbangkan untuk menggunakan blok try-catch untuk menangkap kesalahan dan memberikan pesan kesalahan yang informatif.
- Pertimbangkan Kinerja: Waspadai implikasi kinerja dari hook Anda. Hindari render ulang yang tidak perlu dan optimalkan kode Anda untuk kinerja. Gunakan React.memo, useMemo, dan useCallback untuk mengoptimalkan kinerja jika sesuai.
Kesimpulan
Komposisi custom hook React adalah teknik yang kuat untuk mengabstraksi logika kompleks dan meningkatkan penggunaan kembali kode, pemeliharaan, dan kemampuan uji. Dengan memecah tugas-tugas kompleks menjadi hook yang lebih kecil dan lebih mudah dikelola, Anda dapat membuat basis kode yang lebih modular dan terorganisir. Dengan mengikuti praktik terbaik yang diuraikan dalam artikel ini, Anda dapat secara efektif memanfaatkan komposisi custom hook untuk membangun aplikasi React yang kuat dan skalabel. Ingatlah untuk selalu memprioritaskan kejelasan dan kesederhanaan dalam kode Anda, dan jangan takut untuk bereksperimen dengan pola komposisi yang berbeda untuk menemukan apa yang paling sesuai dengan kebutuhan spesifik Anda.