Tugas utama komponen adalah menghitung tampilan dari state dan props. Tapi kadang komponen perlu menyentuh sesuatu di luar tampilannya sendiri: mengganti judul tab browser, mencatat ke console, menyimpan ke localStorage. Hal-hal begini disebut side effect, dan tempatnya di hook useEffect: kode di dalamnya jalan sesudah React selesai memperbarui halaman. Bayangin teller bank dan papan nomor antrean di dinding. Teller melayani di mejanya, itu render. Papan di dinding ada di luar meja, dan diganti sesudah teller memanggil nomor berikutnya, itu effect. Papannya cuma perlu diganti kalau nomornya berubah, bukan tiap kali teller ganti pulpen. Daftar "ganti kalau ini berubah" itu namanya dependency array.
🧪 Tebakan cepat: sebuah effect mengganti judul tab jadi
"Klik: " + count, dengan dependency[count]. Kamu menekan tombol ganti tema, yang mengubah state lain, dan komponennya render ulang. Effect-nya jalan lagi atau gak? Tebak dulu, jawabannya ada di bagian "Dependency array: kapan effect jalan lagi".
Side effect di useEffect
import { useEffect } from "react"
function App() {
useEffect(() => {
console.log("Komponen dimuat!")
}, [])
return <p>Cek console</p>
}
useEffectmenerima dua hal: function berisi side effect-nya, dan array dependency.- React menjalankan function itu sesudah render pertama tampil di halaman. Saat komponen pertama kali muncul di halaman disebut mount.
[]kosong artinya effect jalan sekali, saat mount. Render-render berikutnya gak menjalankannya lagi.- Hasilnya: console mencetak
Komponen dimuat!satu kali. - Di editor latihan,
useEffectdanuseStatesudah tersedia. Baris import boleh ditulis seperti di proyek sungguhan, tapi gak wajib.
Judul tab juga di luar tampilan komponen, jadi menggantinya adalah side effect:
function App() {
useEffect(() => {
document.title = "Belajar React" // judul tab browser
}, [])
return <h1>Lihat judul tab!</h1>
}
Kenapa gak ditulis langsung di badan komponen? Kode di badan komponen jalan tiap render, dan render sebaiknya cuma menghitung tampilan. Di dalam useEffect, kamu yang menentukan kapan side effect jalan.
Dependency array: kapan effect jalan lagi
function Counter() {
const [count, setCount] = useState(0)
const [dark, setDark] = useState(false)
useEffect(() => {
document.title = `Klik: ${count}`
}, [count])
return (
<div className={dark ? "gelap" : "terang"}>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setDark(!dark)}>Ganti tema</button>
</div>
)
}
Sesudah tiap render, React membandingkan isi array dengan isinya di render sebelumnya. Ada yang berubah? Effect jalan. Semuanya sama? Effect dilewati.
| Kejadian | Render ulang? | count |
Effect [count] |
|---|---|---|---|
| Mount | ya | 0 | jalan, judul Klik: 0 |
| Klik +1 | ya | 1 | jalan, karena 0 jadi 1. Judul Klik: 1 |
| Ganti tema | ya | 1 | dilewati, count masih 1 |
| Klik +1 | ya | 2 | jalan, judul Klik: 2 |
Ganti tema memang membuat render ulang, tapi count gak berubah, jadi effect-nya gak jalan. Itu jawaban tebakannya.
| Ditulis | Effect jalan |
|---|---|
useEffect(fn, []) |
sekali, saat mount |
useEffect(fn, [count]) |
saat mount, lalu tiap count berubah |
useEffect(fn, [a, b]) |
saat mount, lalu tiap a atau b berubah |
useEffect(fn) |
sesudah setiap render |
Tanpa array sama sekali, effect jalan sesudah setiap render. Komponen yang render 3 kali (render pertama ikut dihitung) menjalankan effect-nya 3 kali.
Cleanup, dan kenapa log-nya bisa dobel
Kalau effect me-return sebuah function, function itu disebut cleanup. React memanggilnya sebelum effect yang sama jalan lagi, dan saat komponen dilepas dari halaman (unmount). Di Lab, tiap effect jalan yang baru didahului baris cleanup milik effect sebelumnya. Cara memakainya dibahas di pelajaran useEffect Lanjutan.
Saat development, project React biasanya dibungkus <StrictMode>. Di mode itu React sengaja menjalankan effect saat mount dua kali (effect, cleanup, effect lagi) untuk memeriksa cleanup-mu. Jadi Komponen dimuat! bisa tercetak dua kali di laptopmu. Di build production, cuma sekali. Ini bukan bug di kodemu.
⚠️ Jebakan yang sering ketemu
- Array kosong padahal effect memakai state.
useEffect(() => { document.title = "Klik: " + count }, [])membuat judulnya berhenti diKlik: 0. Masukkan yang dipakai ke array:[count]. - Lupa array, lalu effect mengubah state. Tanpa array effect jalan sesudah tiap render. Kalau isinya
setX(...), itu memicu render, yang menjalankan effect lagi, terus-menerus. - Side effect di badan komponen.
document.title = ...di luaruseEffectjalan di tengah render, tiap render. Taruh di dalam effect. - Effect untuk menghitung nilai.
useEffect(() => setTotal(a + b), [a, b])cuma bikin render tambahan. Hitung langsung:const total = a + b.
TL;DR: Side effect (ganti document.title, console.log, localStorage) ditaruh di useEffect(fn, deps), yang jalan sesudah render tampil di halaman. [] artinya sekali saat mount, [count] artinya saat mount dan tiap count berubah, tanpa array artinya sesudah setiap render (render 3 kali, effect 3 kali). Render yang gak mengubah isi array melewati effect-nya. Function yang di-return effect adalah cleanup, jalan sebelum effect berikutnya dan saat unmount. Di development, StrictMode menjalankan effect mount dua kali, di production sekali.