Kenapa ini penting
Kamu mungkin pernah melihat komponen dengan props seperti ini: <Card title="..." subtitle="..." showIcon iconType="star" footerText="..." showFooterButton footerButtonLabel="...">. Setiap kebutuhan baru menambah satu props lagi. Lama-lama komponennya penuh if dan sulit dirawat. React menyarankan cara lain: komposisi, yaitu menyusun komponen kecil di dalam komponen lain seperti menyusun kotak di dalam kotak. Komposisi juga merupakan obat pertama untuk masalah prop drilling (props dioper melewati banyak lapisan yang tidak membutuhkannya), sebelum kamu meraih Context.
Konsep inti
children: lubang serbaguna
Setiap komponen otomatis menerima props spesial bernama children, yaitu apa pun yang kamu tulis di antara tag pembuka dan penutupnya. <Card><p>Halo</p></Card> berarti children milik Card adalah <p>Halo</p>. Komponen Card cukup menaruh {children} di tempat yang diinginkan. Card tidak perlu tahu isinya apa: teks, gambar, form, apa saja.
Analogi: children itu seperti bingkai foto. Bingkai mengurus bentuk dan warna pinggiran, fotonya bebas diganti.
Slot lewat props
Kadang satu lubang tidak cukup. Halaman punya header, sidebar, dan konten. Di React, props bisa berisi JSX, jadi kamu bisa membuat beberapa "slot" bernama: <Layout header={<Navbar />} sidebar={<Menu />}>...</Layout>. Ini mirip konsep slot di Vue atau Web Components, tapi di React cukup memakai props biasa.
Komposisi vs konfigurasi
| Konfigurasi (props data) | Komposisi (props JSX/children) |
|---|---|
<Card title="Promo" icon="star" /> |
<Card><Card.Title>Promo</Card.Title>...</Card> |
| Komponen menentukan tampilan isi | Pemakai menentukan isi, komponen menentukan bingkai |
| Cocok untuk komponen kecil yang seragam | Cocok untuk komponen wadah yang isinya beragam |
| Setiap variasi baru = props baru | Variasi baru cukup susun isi berbeda |
Keduanya tidak saling meniadakan. Button dengan props varian (konfigurasi) itu wajar. Tapi untuk komponen wadah seperti Card, Modal, Layout, komposisi biasanya lebih tahan lama.
Menghindari prop drilling dengan komposisi
Prop drilling terjadi ketika App punya data user, lalu harus mengopernya ke Layout, lalu ke Header, lalu ke Avatar, padahal Layout dan Header tidak memakai user sama sekali. Mereka hanya "kurir".
Solusi komposisi: biarkan App membuat <Avatar user={user} /> sendiri, lalu mengirimkannya sebagai JSX ke slot Layout. Layout cukup menaruh JSX itu, tanpa pernah tahu soal user. Rantai kurir terputus. Banyak kasus prop drilling selesai dengan cara ini tanpa Context sama sekali. Context baru dipakai kalau data dibutuhkan di banyak tempat yang tersebar (modul berikutnya).
Komposisi di dunia nyata
Hampir semua library komponen populer memakai komposisi. Komponen Card di banyak design system menyediakan potongan seperti CardHeader, CardContent, dan CardFooter yang kamu susun sendiri. Router menerima elemen halaman lewat props (element={<Beranda />}). Library layout menyediakan slot untuk sidebar dan header. Setelah memahami modul ini, kamu akan mengenali pola tersebut dan bisa memakainya dengan percaya diri.
Seberapa banyak prop drilling yang masih wajar?
Mengoper props satu sampai dua level adalah hal biasa dan justru membuat aliran data mudah dilacak: kamu bisa membaca kode dari atas ke bawah dan melihat persis dari mana data datang. Prop drilling baru menjadi masalah ketika: (1) banyak komponen perantara harus menerima props yang tidak mereka pakai, (2) setiap kali menambah data baru kamu harus mengubah lima file, atau (3) data yang sama dibutuhkan di banyak cabang yang berjauhan.
Urutan solusinya: coba komposisi dulu (modul ini). Kalau data benar-benar dibutuhkan di banyak tempat yang tersebar, baru pakai Context (modul berikutnya). Urutan ini menjaga kode tetap eksplisit selama mungkin.
Contoh kode
Komponen Card dengan children dan Modal dengan beberapa slot:
// Card.jsx
export function Card({ children }) {
return <div style={{ border: '1px solid #ddd', borderRadius: 12, padding: 16 }}>{children}</div>;
}
// Modal.jsx, beberapa slot: title, footer, dan children sebagai isi
export function Modal({ title, footer, children, onClose }) {
return (
<div className="backdrop" onClick={onClose}>
{/* stopPropagation supaya klik di dalam kotak tidak menutup modal */}
<div className="modal" onClick={(e) => e.stopPropagation()}>
<header>{title}</header>
<section>{children}</section>
{footer && <footer>{footer}</footer>}
</div>
</div>
);
}
// Pemakaian Modal
import { useState } from 'react';
import { Modal } from './Modal.jsx';
export default function HapusAkun() {
const [buka, setBuka] = useState(false);
return (
<>
<button onClick={() => setBuka(true)}>Hapus akun</button>
{buka && (
<Modal
title={<h2>Yakin?</h2>}
footer={
<>
<button onClick={() => setBuka(false)}>Batal</button>
<button className="danger">Hapus</button>
</>
}
onClose={() => setBuka(false)}
>
<p>Semua data kamu akan hilang permanen.</p>
</Modal>
)}
</>
);
}
Memutus prop drilling dengan slot:
// SEBELUM: user dioper lewat Layout dan Header yang tidak memakainya
function AppLama({ user }) {
return <LayoutLama user={user} />;
}
function LayoutLama({ user }) {
return <HeaderLama user={user} />;
}
function HeaderLama({ user }) {
return <nav>Logo <Avatar user={user} /></nav>;
}
// SESUDAH: App merakit Avatar sendiri, Layout & Header hanya menerima JSX
function Avatar({ user }) {
return <img src={user.foto} alt={user.nama} width={32} />;
}
function Header({ kanan }) {
return <nav>Logo {kanan}</nav>;
}
function Layout({ headerKanan, children }) {
return (
<>
<Header kanan={headerKanan} />
<main>{children}</main>
</>
);
}
export default function App() {
const user = { nama: 'Sari', foto: '/sari.jpg' };
return (
<Layout headerKanan={<Avatar user={user} />}>
<p>Selamat datang, {user.nama}!</p>
</Layout>
);
}
// Layout dan Header tidak lagi punya props user sama sekali
Kesalahan umum
- Lupa merender
{children}. Komponen menerima isi tapi tidak menaruhnya di JSX, sehingga isinya tidak tampil. Pastikan{children}ada di return. - Menambah props boolean untuk setiap variasi.
showFooter,showIcon,showBadgeadalah tanda komponen perlu diubah ke komposisi. - Langsung memakai Context untuk prop drilling dua-tiga level. Context membuat aliran data lebih tersembunyi. Coba dulu komposisi, atau terima saja oper props dua level karena itu masih wajar.
- Mengirim komponen, bukan elemen.
header={Navbar}mengirim fungsinya, bukan hasil render. Yang benarheader={<Navbar />}, kecuali komponen penerima memang dirancang menerima tipe komponen. - Memodifikasi
childrendengan cara rumit. MenggunakanReact.Children.mapataucloneElementuntuk menyuntik props ke children sulit dirawat. Lebih baik pakai Context (seperti compound component) atau slot eksplisit.
Latihan
- Mudah, Buat komponen
Panelyang menerimajudul(string) danchildren, lalu tampilkan judul tebal di atas isinya. - Sedang, Buat komponen
PageLayoutdengan slotsidebar,header, danchildren. Gunakan untuk halaman dashboard sederhana. - Menantang, Ada rantai
App → Dashboard → Toolbar → ThemeToggledi manatemadansetTemadioper sampai keThemeToggle. Refactor dengan komposisi agarDashboarddanToolbartidak menerima props tema.
Lihat solusi
// 1.
export function Panel({ judul, children }) {
return (
<section>
<strong>{judul}</strong>
<div>{children}</div>
</section>
);
}
// <Panel judul="Statistik"><p>100 pengunjung</p></Panel>
// 2.
export function PageLayout({ header, sidebar, children }) {
return (
<div style={{ display: 'grid', gridTemplateColumns: '200px 1fr' }}>
<header style={{ gridColumn: '1 / 3' }}>{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}
export default function Dashboard() {
return (
<PageLayout header={<h1>Dashboard</h1>} sidebar={<nav>Menu</nav>}>
<p>Ringkasan penjualan hari ini.</p>
</PageLayout>
);
}
// 3.
import { useState } from 'react';
function ThemeToggle({ tema, setTema }) {
return <button onClick={() => setTema(tema === 'terang' ? 'gelap' : 'terang')}>Tema: {tema}</button>;
}
function Toolbar({ aksi }) {
return <div className="toolbar">{aksi}</div>;
}
function Dashboard({ toolbarAksi }) {
return (
<>
<Toolbar aksi={toolbarAksi} />
<p>Isi dashboard</p>
</>
);
}
export default function App() {
const [tema, setTema] = useState('terang');
return <Dashboard toolbarAksi={<ThemeToggle tema={tema} setTema={setTema} />} />;
}
Ringkasan
childrenberisi apa pun yang ditulis di antara tag komponen; komponen wadah cukup merender{children}.- Props bisa berisi JSX, sehingga kamu bisa membuat beberapa slot bernama.
- Komposisi lebih fleksibel daripada menambah props konfigurasi untuk setiap variasi.
- Prop drilling sering bisa diputus dengan merakit elemen di atas lalu mengirimnya sebagai slot.
- Pakai Context hanya ketika data dibutuhkan di banyak tempat yang tersebar.