Episode ini membahas useState untuk local state, sistem synthetic events, controlled components untuk form input, serta cara React melakukan batching update dan re-render. Semua dibangun di atas komponen dan JSX dari episode sebelumnya.

Komponen yang statis hanya menampilkan data; aplikasi yang menarik adalah aplikasi yang bereaksi terhadap pengguna. Interaksi dimulai dari dua hal: state yang menyimpan nilai berubah, dan event handling yang menangkap aksi pengguna. Episode 5 membuat kalian menguasai keduanya.
Kita membahas useState untuk local state management, sistem synthetic events milik React, controlled components untuk form, lalu cara React melakukan batching dan re-render. Ini fondasi interaktivitas yang dipakai di semua episode berikutnya.
Hook useState mengembalikan sepasang nilai: nilai sekarang dan fungsi untuk memperbaruinya. Komponen akan di-render ulang setiap kali fungsi update dipanggil:
import { useState } from "react"
function Counter() {
const [hitung, setHitung] = useState(0)
return (
<div>
<p>Hitungan: {hitung}</p>
<button onClick={() => setHitung(hitung + 1)}>Tambah</button>
</div>
)
}useState(0) menetapkan nilai awal 0 dan mengembalikan hitung plus setHitung. Memanggil setHitung selalu mengganti nilai dengan nilai baru dan memicu re-render — ini satu-satunya cara mengubah state yang benar.
Mengubah nilai state secara langsung, seperti hitung = hitung + 1, tidak memicu render apa pun. React mendeteksi perubahan lewat fungsi update. Jika nilai baru bergantung pada nilai lama, gunakan bentuk callback:
const tambah = () => setHitung((sekarang) => sekarang + 1)setHitung((sekarang) => sekarang + 1) menerima fungsi yang diberi nilai state paling baru. Bentuk ini wajib ketika ada beberapa update berurutan atau saat nilai baru dihitung dari state lama.
Event di React ditulis dengan awalan on dan huruf kapital, seperti onClick, onChange, dan onSubmit, dengan handler sebagai nilai:
function FormSederhana() {
const handleKlik = () => alert("Tombol diklik")
const handleTeks = (e) => console.log("Diketik:", e.target.value)
return (
<div>
<button onClick={handleKlik}>Klik</button>
<input onChange={handleTeks} placeholder="Ketik di sini" />
</div>
)
}onClick={handleKlik} menyerahkan referensi fungsi, bukan hasil pemanggilan. Jangan menulis onClick={handleKlik()} kecuali kalian memang ingin memanggilnya saat render.
React membungkus event browser ke dalam SyntheticEvent agar perilakunya konsisten di semua browser. Event ini memiliki sifat yang sama seperti event asli, termasuk preventDefault dan stopPropagation, tapi dipasang lewat delegation ke root DOM sehingga penggunaan memori lebih hemat. Nama properti mengikuti camelCase, misalnya onChange, bukan onchange.
Pada controlled component, nilai input disimpan di state dan dikembalikan lewat atribut value. Setiap ketikan melewati onChange yang memperbarui state, sehingga state selalu sinkron dengan tampilan:
import { useState } from "react"
function FormNama() {
const [nama, setNama] = useState("")
const handleSubmit = (e) => {
e.preventDefault()
console.log("Dikirim:", nama)
}
return (
<form onSubmit={handleSubmit}>
<input value={nama} onChange={(e) => setNama(e.target.value)} />
<button type="submit">Simpan</button>
</form>
)
}e.target.value membaca teks terkini dari event, lalu setNama menyimpannya ke state. Karena value selalu berasal dari state, nilai input tidak pernah menyimpang dari yang React ketahui — pola ini menjadi dasar validasi langsung di episode 11.
React menggabungkan beberapa pemanggilan setter dalam satu event menjadi satu render. Karena itu, membaca state lama di tengah batch tidak akan melihat nilai yang baru:
const handleTambah = () => {
setHitung(hitung + 1)
setHitung(hitung + 1)
}Dua pemanggilan di atas hanya menambah hitung sebanyak satu, karena keduanya membaca nilai yang sama dari render sebelumnya. Gunakan bentuk callback agar setiap update memakai hasil update sebelumnya:
const handleTambah = () => {
setHitung((sekarang) => sekarang + 1)
setHitung((sekarang) => sekarang + 1)
}setHitung((sekarang) => sekarang + 1) dijalankan berurutan, sehingga hasil akhirnya hitung bertambah dua. React menjalankan callback satu per satu di dalam satu batch render.
Ketika state berubah, React menjalankan ulang fungsi komponen dan membandingkan hasilnya dengan render sebelumnya lewat reconciliation. Hanya bagian yang benar-benar berubah yang diperbarui ke DOM. Memahami pola ini membantu kalian menghindari render boros — topik yang dibahas lebih dalam di episode 6 dan 15.
Episode 5 memberi kalian otak untuk aplikasi: useState untuk menyimpan dan memperbarui nilai, synthetic events untuk menangkap interaksi, controlled components untuk form yang selalu sinkron, serta pemahaman batching dan re-render yang menjadi kunci membaca perilaku React.
Inti yang harus dibawa pulang:
useState mengembalikan nilai dan fungsi update; jangan pernah mutasi state langsung.value dan onChange.Di episode 6 selanjutnya kita akan membahas effects & lifecycle dengan hooks — useEffect untuk side effects, cleanup effect dan dependency array, useMemo dan useCallback untuk optimasi, serta custom hooks untuk logika yang dipakai ulang. Aplikasi kalian mulai keluar dari layar: memuat data, mendengarkan event global, dan mengelola timer.