Mengapa JSX ada
Bab 12 memperlihatkan merender dengan tangan itu panjang. Satu baris data butuh createElement, textContent, appendChild. Tiga panggilan untuk satu elemen, dan pembacanya harus menyusuri urutan perintah dulu sebelum paham bentuk akhirnya. React menawarkan jalan lain. Di dalam komponen, fungsi yang mengembalikan bentuk tampilan, kita menulis bentuk halaman itu seperti HTML. Cara menulis ini disebut JSX, JavaScript XML, sintaksis yang mirip HTML tapi hidup di dalam kode JavaScript. JSX bukan HTML sungguhan. Ia gula mata, bentuknya familiar, isinya JavaScript.
Bagaimana JSX bekerja
Browser tidak pernah mengerti JSX. JSX bukan JavaScript yang sah. Sebelum sampai ke browser, JSX dikompilasi, diubah oleh program lain menjadi panggilan fungsi biasa. Compiler seperti Babel, esbuild, atau SWC mengerjakan ini di proyek sungguhan. Padanannya sederhana.
<li>{tugas.judul}</li>
React.createElement("li", null, tugas.judul);
Jadi setiap kali menulis JSX, yang sebenarnya dieksekusi adalah fungsi createElement. Melihat padanan ini membuat aturan JSX masuk akal, bukan hafalan. Satu return wajib membungkus satu induk, karena createElement mengembalikan satu nilai. class menjadi className, karena class adalah kata tercadang JavaScript. Nilai JavaScript masuk lewat kurung kurawal {}, sehingga kondisi bab 12 ikut muat, {tugas.length === 0 && <p>Belum ada tugas.</p>}. Tag tunggal ditutup sendiri, <img /> dan <input />, seperti XML.
Props: data yang mengalir turun
Komponen adalah fungsi biasa, dan fungsi menerima argumen. Argumen komponen bernama props, properti yang dikirim induknya lewat atribut pada tag komponen. <Baris nama="es teh" jumlah={2} /> sama artinya dengan memanggil fungsi Baris dengan objek { nama: "es teh", jumlah: 2 }. Anak memakai props untuk menampilkan sesuatu. Aturannya satu, dibaca saja. Anak tidak mengubah props miliknya, karena data itu milik induk. Semuanya mengalir satu arah, dari atas ke bawah. Kalau anak ingin data berubah, ia tidak mencoret kartunya sendiri, ia minta induk lewat fungsi yang induk kirim sebagai props.
State: ingatan yang memicu render ulang
Di sinilah masalah muncul. Komponen React adalah fungsi yang dijalankan berulang-ulang, dan variabel lokal fungsi mati setiap fungsi selesai, seperti diajarkan bab 7. Padahal tampilan butuh ingatan, isi keranjang, jumlah pesanan, teks kotak pencarian. React menyimpan ingatan itu sendiri, di luar fungsi komponen, lalu menyambungkannya lewat hook bernama useState. Hook, fungsi khusus React yang hanya boleh dipanggil di dalam komponen.
const [hitung, setHitung] = React.useState(0);
useState(0) memberi dua barang sekaligus. hitung, isi ingatan sekarang. setHitung, satu-satunya pintu untuk menggantinya. Memanggil setHitung(1) melakukan dua hal. Mengganti isi ingatan, lalu menjadwalkan render ulang, React menjalankan fungsi komponen lagi dari atas, dan kali ini fungsi membaca nilai baru. Di sinilah perbedaan inti dengan vanilla. Di vanilla, mengganti variabel tidak mengubah apa pun di layar sampai kita memanggil render() dengan tangan. Di React, panggilan itu otomatis, dipicu oleh setHitung. Bab 12 sudah menyebut React membandingkan hasil render baru dengan halaman lama dan menyentuh DOM hanya di bagian yang berubah. Bab ini menunjukkan pemicunya, panggilan setHitung.
Analogi: dapur restoran
Satu gambar untuk semuanya. Di dapur restoran ada kartu resep penyajian, ditulis sekali, bentuknya tetap, dengan lubang isian untuk nama menu dan catatan. Itu JSX, cetakan bentuk tampilan. Di rel dapur terselip kartu pesanan dari pelanggan, isinya dari luar dapur, koki hanya membaca dan tidak boleh mencoretnya. Itu props, data yang mengalir turun dan dibaca saja. Di dinding dapur ada papan catatan milik koki, "es teh tinggal 2 porsi". Saat koki mengganti catatan itu menjadi tinggal 1, ia menyajikan ulang meja sesuai catatan terbaru. Itu state, ingatan internal yang setiap kali diganti memicu pekerjaan ulang. Koki tidak menghafal tiga istilah ini, ia memakai tiga benda yang masing-masing punya aturan main.
Pasangan contoh
Contohnya kedai sederhana. Tiga tombol menu, nasi goreng, es teh, bakso. Tiap klik menambah satu pesanan, daftar di bawah menunjukkan jumlah per menu. Versi vanilla menyimpan hitungan di objek, memasang addEventListener bab 9, lalu memanggil render() dengan tangan setiap klik. Versi React menyimpan hitungan di useState, dan induk Kedai mengirim nama dan jumlah sebagai props ke komponen anak Baris lewat map dengan key bab 12.
Vanilla JavaScript
const MENU = ["nasi goreng", "es teh", "bakso"];
const hitung = { "nasi goreng": 0, "es teh": 0, bakso: 0 };
const daftar = document.querySelector("#pesanan");
document.querySelectorAll("[data-menu]").forEach(function (tombol) {
tombol.addEventListener("click", function () {
const nama = tombol.dataset.menu;
hitung[nama] = hitung[nama] + 1;
render();
});
});
function render() {
daftar.replaceChildren();
MENU.forEach(function (nama) {
if (hitung[nama] === 0) return;
const li = document.createElement("li");
li.textContent = nama + " x " + hitung[nama];
daftar.appendChild(li);
});
}
React
function Baris(props) {
return <li>{props.nama} x {props.jumlah}</li>;
}
function Kedai() {
const [hitung, setHitung] = React.useState({});
const menu = ["nasi goreng", "es teh", "bakso"];
function tambah(nama) {
setHitung({ ...hitung, [nama]: (hitung[nama] || 0) + 1 });
}
return (
<div>
{menu.map(function (nama) {
return (
<button key={nama} onClick={function () { tambah(nama); }}>
+ {nama}
</button>
);
})}
<ul>
{menu.filter(function (n) { return hitung[n]; }).map(function (nama) {
return <Baris key={nama} nama={nama} jumlah={hitung[nama]} />;
})}
</ul>
</div>
);
}
Perbedaan inti bab ini ada di satu pertanyaan, siapa yang memanggil render ulang. Versi vanilla mengganti objek hitung lalu harus ingat memanggil render() sendiri. Lupa panggilan itu, layar tertinggal. Versi React mengganti hitungan lewat setHitung, dan render ulang dijadwalkan otomatis, fungsi Kedai dijalankan lagi dari atas dengan nilai baru. Props ikut kerja di sini. Data tidak tersebar di variabel lepas, tetapi mengalir turun dari induk ke tiap Baris, sehingga sumber kebenaran cuma satu, state milik Kedai. Di vanilla, pembagian tugas itu dibuat sendiri dengan disiplin. Di React, kerangka yang memaksa.
Cara React 19 mempermudah form
React 19 menambahkan Actions, cara menangani form dan aksi dari dalam satu kejadian. Sebelumnya, form React mengurus nilai setiap input lewat useState satu per satu, lalu menolak kirimnya dengan tangan. Actions membungkus itu ke satu hook bernama useActionState, yang menerima fungsi aksi dan status awal form, lalu mengembalikan status itu bersama pengirim baru yang dipakai di atribut action form.
function FormNama() {
const [hasil, kirimForm] = React.useActionState(
async function (state, formData) {
const nama = formData.get("nama");
await fetch("/api-sapa", { method: "POST", body: formData });
return "Halo, " + nama + "!";
},
""
);
return (
<form action={kirimForm}>
<input name="nama" placeholder="Nama kamu" />
<button type="submit">Kirim</button>
<p>{hasil}</p>
</form>
);
}
Dua hal pantas disorot. Pertama, aksinya bisa async, menunggu data terkirim dulu sebelum menampilkan hasil, tanpa useEffect. Kedua, isi form dibaca lewat FormData, bukan per-input useState. Actions bukan materi wajib; buku ini cukup dengan useState untuk state biasa. Tapi membaca dokumentasi React hari ini, istilah ini muncul terus, jadi mengenal bentuknya membantu saat nanti berjumpa.
Catatan versi. React 19.3 menambahkan komponen bernama <ViewTransition> yang membuat perubahan tampilan bergerak halus, memakai View Transition API milik browser. Bungkus bagian yang bisa muncul, hilang, atau berpindah dengan komponen itu, dan saat bagian tersebut berubah dalam sebuah update yang ditandai sebagai transition, browser menganimasikannya secara lintas-fade alih-alih langsung melompat ke tampilan baru. Contoh paling sederhana satu kotak yang muncul dan hilang.
function Kotak() {
return (
<React.ViewTransition>
<div className="kotak">Halo, Kuka!</div>
</React.ViewTransition>
);
}
function Aplikasi() {
const [tampil, setTampil] = React.useState(false);
return (
<div>
<button
onClick={() => React.startTransition(
() => setTampil(t => !t)
)}>
{tampil ? "Sembunyikan" : "Tampilkan"}
</button>
{tampil && <Kotak />}
</div>
);
}
Peran keduanya terbagi jelas. startTransition menandai pembaruan state sebagai transisi React, bukan langsung memulai animasi, lalu React meminta browser menganimasikan bagian yang dibungkus <ViewTransition>. Ada satu syaratnya, batas <ViewTransition> harus jadi node teratas pada output komponennya, makanya kotak dipisah jadi komponen sendiri dan tombolnya tinggal di komponen induk. Perlu diingat, ViewTransition memerlukan React 19.3, sementara halaman-halaman buku ini mengunci React 19.2.8 supaya demo CDN-nya stabil. Jadi catatan ini cukup jadi konsep dan kode contoh, bukan demo hidup, dan tidak mengganti pendekatan render ulang yang sudah dipelajari bab ini.
Demo Hidup
Demo di bawah menyalin kedai yang sama dan benar-benar jalan di halaman ini. Satu rintangan teknis, browser tidak bisa mengeksekusi JSX mentah, dan halaman ini tanpa build tool. Jadi demo memuat React lewat import map dari CDN ESM, lalu memakai jembatan bernama htm, yang bentuknya mirip JSX tapi ditulis di dalam template literal bab 12. Yang dibaca htm dan yang dibaca compiler JSX berakhir di panggilan createElement yang sama. Karena dimuat dari CDN, demo ini butuh koneksi internet, beda dengan demo bab sebelumnya yang jalan tanpa jaringan.
Tiga tombol di bawah adalah menu kedai. Tekan beberapa kali, perhatikan daftar dan total yang berganti sendiri tanpa satu pun panggilan render() yang ditulis dengan tangan.
Memuat React dari CDN...
Begini cara baca demonya. Fungsi Kedai memegang satu state, objek hitung, lewat useState({}). Tiap tombol menu memanggil tambah(nama), dan tambah memanggil setHitung dengan objek baru. Panggilan itulah yang memicu render ulang, fungsi Kedai dijalankan lagi dari atas dan membaca hitungan terbaru. Daftar pesanan dirender lewat filter dan map, tiap baris dipanggil sebagai komponen Baris dengan props nama dan jumlah. Baris tidak menyimpan apa pun, ia membaca kartu pesanannya. Bedanya dengan kode di <script> halaman ini cuma satu, html\`...\` dari htm menggantikan JSX, karena JSX mentah butuh compiler dan halaman ini tanpa build tool.
Ringkasan bab ini. JSX adalah cara menulis bentuk tampilan yang mirip HTML di dalam JavaScript, dan ia dikompilasi menjadi panggilan createElement sebelum browser melihatnya. Props adalah data yang dikirim induk ke anak lewat atribut, dibaca saja, mengalir satu arah. State adalah ingatan milik komponen yang dipegang React, diganti lewat fungsi setHitung dari useState, dan tiap pergantian menjadwalkan render ulang. Bab berikutnya memakai ketiganya untuk menjalankan efek dan mengambil data dari server.