Kenapa ini penting
Di modul sebelumnya, tiga KartuProduk kita identik semua. Tidak berguna, kan? Toko butuh kartu dengan nama dan harga berbeda. Komponen menjadi benar-benar berguna saat bisa menerima data dari luar, persis seperti fungsi yang menerima argumen. Di React, data ini disebut props (kependekan dari properties). Props juga satu-satunya jalur resmi untuk komunikasi dari induk ke anak, dan lewat props berisi fungsi (callback), anak bisa "melapor" ke induknya. Memahami aliran data ini adalah fondasi semua aplikasi React.
Konsep inti
Props: argumen untuk komponen
Props ditulis seperti atribut HTML saat memakai komponen, dan diterima sebagai satu objek di parameter pertama fungsi komponen. Cara paling umum adalah langsung destructuring: function KartuProduk({ nama, harga }), lalu dipakai <KartuProduk nama="Kopi Gayo" harga={85000} />.
String boleh pakai tanda kutip, nilai lain (angka, boolean, array, objek, fungsi) pakai {}.
Analogi: komponen adalah cetakan kue, props adalah topping yang kamu pilih. Cetakannya sama, hasilnya berbeda sesuai topping.
Props bersifat read-only
Komponen tidak boleh mengubah props-nya sendiri. Props itu milik induk. Kalau data perlu berubah, itu urusan state (modul berikutnya). Menulis props.harga = 0 melanggar prinsip kemurnian komponen.
Aliran data satu arah (one-way data flow)
Data mengalir dari atas ke bawah: induk ke anak. Anak tidak bisa langsung mengubah data induk. Ini membuat aplikasi mudah ditelusuri: kalau ada data salah, kamu tinggal mencari ke atas dari mana asalnya.
Nilai default
Untuk function component, nilai default ditulis langsung di destructuring parameter, sesuai cara di dokumentasi react.dev: function Avatar({ size = 100 }). Nilai default dipakai ketika prop tidak dikirim atau bernilai undefined (tapi tidak ketika null). Properti defaultProps adalah gaya lama yang berasal dari class component; untuk function component, pakai default parameter.
children
Apa pun yang kamu tulis di antara tag pembuka dan penutup komponen akan diterima sebagai prop khusus bernama children. Ini memungkinkan komponen "pembungkus" seperti Kartu, Modal, atau Layout yang tidak peduli isinya apa.
Analogi: children seperti bingkai foto. Bingkainya tetap, fotonya bisa diganti apa saja.
Callback: anak berkomunikasi ke induk
Karena data mengalir satu arah, bagaimana tombol di dalam anak bisa memberi tahu induk bahwa ia diklik? Induk mengirim fungsi sebagai prop. Anak memanggil fungsi itu saat terjadi sesuatu, boleh sambil mengirim data sebagai argumen. Konvensi nama: prop diawali on (onTambah, onHapus), fungsi handler di induk diawali handle (handleTambah).
Analogi: induk memberikan nomor telepon (fungsi) ke anak. Anak tidak bisa masuk ke rumah induk, tapi bisa menelepon kapan saja.
Props drilling
Kadang data harus melewati beberapa lapis komponen yang sebenarnya tidak membutuhkannya, hanya untuk sampai ke cucu atau cicit. Ini disebut props drilling. Sedikit lapis masih wajar. Kalau sudah lima lapis atau lebih, kode jadi berisik dan rapuh: menambah satu prop baru berarti mengubah semua komponen perantara. Solusinya (dibahas di materi lanjutan) antara lain Context API, atau menyusun ulang komponen dengan children agar data tidak perlu lewat perantara.
Contoh kode
// src/App.jsx, props, default, children, dan callback dalam satu contoh
import { useState } from 'react';
// Komponen pembungkus yang memakai children
function Kartu({ children, judul }) {
return (
<section style={{ border: '1px solid #ddd', borderRadius: 8, padding: 12, marginBottom: 12 }}>
{judul && <h2>{judul}</h2>}
{children}
</section>
);
}
// Props dengan nilai default: stok = 0 dan satuan = 'pcs'
function KartuProduk({ nama, harga, stok = 0, satuan = 'pcs', onTambah }) {
return (
<article>
<h3>{nama}</h3>
<p>Rp {harga.toLocaleString('id-ID')} / {satuan}</p>
<p>Stok: {stok}</p>
{/* Anak memanggil fungsi dari induk sambil mengirim data */}
<button onClick={() => onTambah(nama)}>Tambah ke keranjang</button>
</article>
);
}
export default function App() {
const [keranjang, setKeranjang] = useState([]);
function handleTambah(namaProduk) {
setKeranjang([...keranjang, namaProduk]);
}
return (
<>
<Kartu judul="Menu Kopi">
<KartuProduk nama="Kopi Gayo" harga={85000} stok={12} satuan="250g" onTambah={handleTambah} />
<KartuProduk nama="Kopi Toraja" harga={90000} onTambah={handleTambah} />
</Kartu>
<Kartu judul="Keranjang">
<p>Isi: {keranjang.length} item</p>
<p>{keranjang.join(', ')}</p>
</Kartu>
</>
);
}
// Kartu Kopi Toraja → "Stok: 0" dan "/ pcs" karena memakai nilai default
// Klik "Tambah" di Kopi Gayo lalu Kopi Toraja → "Isi: 2 item", "Kopi Gayo, Kopi Toraja"
Contoh props drilling dan cara meringankannya dengan children:
// ❌ Props drilling: Halaman dan Sidebar tidak memakai "user", hanya meneruskan
function Avatar({ user }) {
return <img src={user.foto} alt={user.nama} width={40} />;
}
function Sidebar({ user }) {
return <aside><Avatar user={user} /></aside>;
}
function Halaman({ user }) {
return <div><Sidebar user={user} /></div>;
}
// ✅ Dengan children: App langsung menaruh Avatar di tempatnya
function SidebarFleksibel({ children }) {
return <aside>{children}</aside>;
}
function HalamanFleksibel({ children }) {
return <div>{children}</div>;
}
export function AppContoh() {
const user = { nama: 'Budi', foto: 'https://i.pravatar.cc/40?img=3' };
return (
<HalamanFleksibel>
<SidebarFleksibel>
<Avatar user={user} />
</SidebarFleksibel>
</HalamanFleksibel>
);
}
Kesalahan umum
- Lupa destructuring atau lupa kurung kurawal di parameter.
function Kartu(nama)membuatnamaberisi seluruh objek props. Yang benarfunction Kartu({ nama }). - Mengirim angka sebagai string.
harga="85000"mengirim string, sehinggaharga + 1000menjadi"850001000". Pakaiharga={85000}. - Mengubah props di dalam komponen. Props read-only. Kalau perlu nilai yang berubah, gunakan state.
- Memanggil callback saat render.
onClick={onTambah(nama)}langsung memanggil fungsi saat render. Bungkus dengan arrow function:onClick={() => onTambah(nama)}. - Membiarkan props drilling terlalu dalam. Kalau data menembus banyak lapis, pertimbangkan
childrenatau Context (materi lanjutan).
Latihan
- Mudah, Buat komponen
Sapayang menerima propnamadengan default'Teman'. Tampilkan<Sapa nama="Andi" />dan<Sapa />. - Sedang, Buat komponen
Alertyang menerimatipe('sukses'atau'error') danchildren. Warna latar hijau untuk sukses, merah muda untuk error. - Menantang, Buat komponen
TombolVoteyang menerimapilihandanonVote. DiApp, simpan jumlah suara untuk "React" dan "Vue" dalam state objek, dan tampilkan hasilnya. Anak hanya boleh memanggilonVote(pilihan).
Lihat solusi
1.
function Sapa({ nama = 'Teman' }) {
return <p>Halo, {nama}!</p>;
}
export default function App() {
return (
<>
<Sapa nama="Andi" /> {/* → Halo, Andi! */}
<Sapa /> {/* → Halo, Teman! */}
</>
);
}
2.
function Alert({ tipe = 'sukses', children }) {
const warna = tipe === 'error' ? '#fde2e2' : '#dcfce7';
return (
<div role="alert" style={{ background: warna, padding: 12, borderRadius: 6 }}>
{children}
</div>
);
}
export default function App() {
return (
<>
<Alert tipe="sukses">Data berhasil disimpan.</Alert>
<Alert tipe="error"><strong>Gagal!</strong> Coba lagi nanti.</Alert>
</>
);
}
3.
import { useState } from 'react';
function TombolVote({ pilihan, onVote }) {
return <button onClick={() => onVote(pilihan)}>Vote {pilihan}</button>;
}
export default function App() {
const [suara, setSuara] = useState({ React: 0, Vue: 0 });
function handleVote(pilihan) {
setSuara({ ...suara, [pilihan]: suara[pilihan] + 1 });
}
return (
<>
<TombolVote pilihan="React" onVote={handleVote} />
<TombolVote pilihan="Vue" onVote={handleVote} />
<p>React: {suara.React} | Vue: {suara.Vue}</p>
</>
);
}
// Klik Vote React 2x, Vote Vue 1x → "React: 2 | Vue: 1"
Ringkasan
- Props adalah data yang dikirim induk ke anak, diterima sebagai satu objek dan biasanya di-destructuring.
- Props bersifat read-only dan data mengalir satu arah dari atas ke bawah.
- Nilai default ditulis di destructuring parameter, misalnya
{ stok = 0 }. childrenberisi apa pun yang ditulis di antara tag pembuka dan penutup komponen.- Anak berkomunikasi ke induk dengan memanggil fungsi callback yang dikirim lewat props; props drilling yang terlalu dalam perlu dirapikan.