Mengirimkan props melalui lapisan komponen yang tidak membutuhkannya adalah pekerjaan yang melelahkan. Suatu hari Anda sedang merilis sebuah fitur, dan keesokan harinya Anda harus mengedit enam file hanya untuk mengubah nama satu prop saja. Itulah yang disebut prop drilling secara singkat: sebuah parent memiliki data, anak yang jauh membutuhkannya, dan setiap komponen di antara mereka menjadi kurir. Aplikasi tetap berjalan, tetapi codebase menjadi rapuh. Hapus satu lapisan tengah, dan setengah dari struktur pohon komponen akan runtuh. Ubah sebuah tipe, dan TypeScript akan protes di tiga direktori berbeda. React Context API hadir untuk menghilangkan para perantara tersebut sepenuhnya.
Seperti Apa Bentuk Prop Drilling yang Sebenarnya
Bayangkan sebuah kerangka aplikasi standar. Anda memiliki komponen App yang mengambil data user saat ini. Di dalam App terdapat Layout, di dalam Layout terdapat Sidebar, di dalam Sidebar bersarang Navigation, dan akhirnya di dalam Navigation Anda menemukan UserAvatar yang sebenarnya membutuhkan objek user tersebut.
Kode Anda akhirnya menjadi seperti ini:
function App() {
const user = { name: 'Aarav', role: 'admin' };
return <Layout user={user} />;
}
function Layout({ user }) {
return <Sidebar user={user} />;
}
function Sidebar({ user }) {
return <Navigation user={user} />;
}
function Navigation({ user }) {
return <UserAvatar user={user} />;
}
Layout, Sidebar, dan Navigation tidak melakukan apa pun dengan objek user tersebut selain meneruskannya ke bawah. Mereka menumpuk props yang bukan milik mereka, interface mereka membengkak, dan untuk mengujinya pun memerlukan mocking data yang bahkan tidak pernah mereka sentuh. Masalah sebenarnya adalah seberapa cepat hal ini menyebar. Tambahkan flag isLoggedIn, string locale, atau nilai theme, dan parade yang sama akan terulang kembali.
Bagaimana Context API Mengubah Segalanya
Bayangkan Context API seperti router WiFi. Alih-alih memasang kabel panjang ke setiap ruangan untuk menjangkau setiap perangkat, router mengirimkan sinyal melalui udara. Perangkat apa pun dalam jangkauan dapat terhubung secara langsung. Dalam istilah React, router adalah Provider, sinyalnya adalah state atau data Anda, dan perangkatnya adalah komponen bersarang mana pun yang memanggil useContext.
Pengaturannya memiliki tiga bagian utama:
React.createContext()membangun saluran data.- Provider membungkus bagian dari pohon komponen Anda dan mengirimkan sebuah nilai.
- Hook
useContextmemungkinkan komponen keturunan menerima nilai tersebut tanpa harus menyentuh props perantara.
Anda tetap memiliki struktur pohon, tetapi cabang-cabang antara akar (root) dan daun (leaf) tidak lagi perlu menyetujui kontrak kurir.
Membangun Context dari Nol
Mari kita buat contoh konkret dengan pengaturan tema, karena sebagian besar aplikasi membutuhkan mode terang atau gelap pada suatu saat.
Pertama, buat objek context. Ini adalah pipanya:
import { createContext, useState, useMemo } from 'react';
const ThemeContext = createContext(null);
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export default ThemeContext;
Kemudian bungkus aplikasi Anda dengan provider. Biasanya ini dilakukan di dekat root:
import { ThemeProvider } from './ThemeContext';
function App() {
return (
<ThemeProvider>
<Layout />
</ThemeProvider>
);
}
Sekarang, keturunan mana pun dapat langsung menangkap sinyalnya. Berikut adalah tombol toggle yang tertanam jauh di dalam UI:
import { useContext } from 'react';
import ThemeContext from './ThemeContext';
function ThemeToggle() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
onClick={() => setTheme(prev => prev === 'light' ? 'dark' : 'light')}
>
Current theme: {theme}
</button>
);
}
Perhatikan bahwa Layout, Sidebar, dan Navigation tidak pernah melihat prop theme. Mereka merender secara normal, dan ThemeToggle mengambil apa yang dibutuhkannya langsung dari context. Pengabelannya tidak terlihat dari luar, dan itulah poin utamanya.
Di Mana Context Seharusnya Digunakan
Context bekerja paling baik untuk data yang dibagikan oleh banyak komponen yang berjauhan tetapi tidak dimiliki secara bersih oleh satu parent saja. Kandidat yang baik meliputi:
- Pengaturan tema seperti mode terang atau gelap, warna aksen, atau penskalaan font.
- Status autentikasi seperti objek user saat ini, status login, atau kedaluwarsa sesi.
- Bahasa dan locale untuk internasionalisasi.
- Data keranjang belanja yang harus tetap sinkron di seluruh badge header, dropdown mini-cart, dan halaman checkout.
Tahan keinginan untuk memasukkan setiap potongan local state ke dalam Context. Input formulir yang berada dua level di bawah tidak memerlukan siaran global. Simpan Context untuk masalah lintas komponen (cross-cutting concerns) yang nyata, dan biarkan sisanya tetap sebagai props biasa.
Jebakan Performa dan Cara Menghindarinya
Context tidaklah gratis. Ketika nilai context diperbarui, setiap komponen yang terhubung ke context tersebut akan melakukan re-render, bahkan jika bagian nilai yang mereka pedulikan tidak berubah. Kesalahan klasik adalah memasukkan literal objek baru ke dalam Provider pada setiap render parent.
Dalam contoh tema kita, setiap kali ThemeProvider melakukan re-render karena parent-nya sendiri diperbarui, ekspresi { theme, setTheme } membuat objek yang benar-benar baru. React melihat referensi baru, dan setiap consumer akan diperbarui. Jika tema Anda jarang berubah tetapi state aplikasi Anda sering berubah, Anda akan membayar untuk re-render yang tidak Anda butuhkan.
Solusinya ada dua.
Pisahkan context Anda berdasarkan frekuensi pembaruan. Sebuah UserContext yang berubah sekali per login tidak boleh berbagi provider dengan NotificationContext yang diperbarui setiap beberapa detik. Biarkan mereka terpisah agar data statis tidak ikut terbawa dalam kereta re-render yang sama dengan data yang volatil.
Bungkus nilai dalam useMemo jika nilainya berupa objek atau array. Berikan React referensi yang stabil:
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
Sekarang identitas objek hanya berubah saat theme benar-benar berubah. Komponen turunan yang peduli terhadap konteks tersebut tetapi terlindungi oleh React.memo di level bawahnya akan melewatkan proses render tersebut.
Kesalahan yang Membuang-buang Waktu Anda
Dua kesalahan yang masih sering muncul di kode produksi sangat mudah untuk dicegah.
Pertama, lupa mengekspor konteks itu sendiri. Jika Anda hanya mengekspor pembungkus Provider dan menjaga objek konteks tetap privat, pengembang yang menulis fitur baru tidak dapat memanggil useContext tanpa melakukan refaktorisasi pada modul Anda. Eksporlah konteks tersebut agar konsumen dapat mengimpor provider sekaligus hook konsumen secara bersih.
Kedua, memanggil useContext di luar Provider yang sesuai. Jika ThemeToggle dirender dalam cabang pohon yang tidak dibungkus dalam ThemeProvider, hook tersebut akan mengembalikan nilai default yang diberikan ke createContext, atau undefined jika Anda tidak memberikan nilai apa pun. Hal ini menyebabkan kegagalan senyap seperti cannot read property of undefined. Anda dapat mencegah hal ini dengan menetapkan nilai default yang masuk akal atau melemparkan error yang jelas di awal pemanggilan hook.
Context vs Redux: Tetap Sederhana
Anda tidak selalu membutuhkan Redux. Untuk proyek berukuran menengah, Context yang dipasangkan dengan useState atau useReducer sudah mencakup sebagian besar kebutuhan berbagi state. Redux sangat unggul saat Anda membutuhkan debugging time-travel, middleware yang kompleks, atau transaksi global yang harus dibatalkan secara berurutan. Jika seluruh skenario state Anda hanyalah sebuah objek user, string tema, dan array keranjang belanja, penggunaan library store hanya akan menambah boilerplate yang tidak akan pernah Anda manfaatkan.
Meski begitu, Context bukanlah sistem manajemen state yang lengkap secara mandiri. Ia tidak memberikan satu snapshot global tunggal, dan tidak melakukan batching update di berbagai konteks yang tidak terkait. Gunakanlah sebagai pengganti prop drilling, bukan sebagai sistem operasi untuk seluruh lapisan data Anda.
Kesimpulan Utama
Berhentilah meneruskan props melalui komponen yang tidak membutuhkannya. Buatlah konteks yang terfokus untuk data yang benar-benar mencakup seluruh pohon komponen Anda, bungkus provider cukup tinggi untuk mencakup para konsumen, dan selalu stabilkan objek nilai saat Anda meneruskan koleksi atau fungsi. Context API menjaga kode React Anda tetap lugas: props tetap lokal, data global berpindah secara "nirkabel", dan batasan komponen Anda tetap bersih.
