Tiap kali state berubah, React memanggil ulang function komponenmu dari atas, dan semua yang ada di dalamnya jalan lagi: hitungan, pembuatan object, pembuatan function. Biasanya itu cepat dan gak masalah. useMemo dan useCallback adalah dua cara untuk mengingat sesuatu dari render sebelumnya, supaya gak dibuat ulang kalau bahannya gak berubah. Bayangin kasir yang baru saja menghitung total belanjaan yang panjang. Kalau pembeli cuma bertanya "totalnya berapa tadi?", kasir membaca catatannya, bukan menghitung ulang semua barang. Dia baru menghitung lagi kalau isi keranjangnya berubah. Isi keranjang itu dependency, catatannya itu nilai yang diingat.
🧪 Tebakan cepat: di Lab,
factorial(n)diberi beban tiruan 80 ms. Kamu mengetikhalodi kolom teks yang gak ada hubungannya dengann. TanpauseMemo, berapa kalifactorialdihitung ulang? Tebak dulu, jawabannya ada di bagian "Tiap render, semua kode jalan lagi".
Tiap render, semua kode jalan lagi
function factorial(n) {
const selesai = performance.now() + 80 // beban tiruan: 80 ms
while (performance.now() < selesai) {}
let hasil = 1
for (let i = 2; i <= n; i++) hasil *= i
return hasil
}
function App() {
const [n, setN] = useState(5)
const [text, setText] = useState("")
const result = factorial(n)
return (
<div>
<p>{n}! = {result}</p>
<input value={text} onChange={(e) => setText(e.target.value)} />
</div>
)
}
factorial(n)menghitung 1 × 2 × ... × n. Dua bariswhiledi atasnya cuma beban tiruan, supaya hitungannya terasa berat seperti filter ribuan data.const result = factorial(n)ada di badan komponen, jadi jalan di tiap render.- Tiap ketikan memanggil
setText, dan React render ulangApp. Hitungannya ikut jalan, walaupunngak berubah.
| Kejadian | Render App ke- |
factorial jalan, tanpa useMemo |
factorial jalan, dengan useMemo |
|---|---|---|---|
| tampil pertama | 1 | 1 | 1 |
| ketik h | 2 | 2 | 1 |
| ketik a | 3 | 3 | 1 |
| ketik l | 4 | 4 | 1 |
| ketik o | 5 | 5 | 1 |
klik Tambah n, n jadi 6 |
6 | 6 | 2 |
Mengetik halo berarti 4 render, jadi tanpa useMemo hitungannya diulang 4 kali: 4 × 80 ms = 320 ms ketikan yang tersendat. Itu jawaban tebakannya.
useMemo: ingat hasil hitungan
const result = useMemo(() => factorial(n), [n])
- Argumen pertama adalah function yang menghitung. Argumen kedua adalah dependency array: daftar nilai yang dipakai hitungan itu.
- Di render pertama, React menjalankan function-nya dan mengingat hasilnya.
- Di render berikutnya, React membandingkan tiap isi dependency dengan render sebelumnya. Semuanya sama? Hasil lama dikembalikan tanpa memanggil function-nya. Ada yang beda? Hitung ulang.
- Mengetik di kolom teks gak mengubah
n, jadi hasil lama, 120, dipakai lagi. Begitunjadi 6, baru dihitung ulang: 720.
useCallback: ingat function-nya
Function juga dibuat baru di tiap render. const handleAdd = () => setN((c) => c + 1) menghasilkan function baru setiap kali, dan dua function yang isinya sama tetap dianggap beda: (() => {}) === (() => {}) bernilai false. Biasanya ini gak masalah. Jadi masalah kalau function itu dikirim sebagai props ke komponen yang dibungkus memo.
memo(Komponen) membuat komponen melewati render ulang kalau semua props-nya sama dengan render sebelumnya. Detailnya ada di materi React.memo. Untuk sekarang cukup tahu: kalau props-nya function baru, memo menganggapnya props baru.
const Child = memo(function Child({ onAdd }) {
console.log("Child rendered")
return <button onClick={onAdd}>Tambah n</button>
})
function App() {
const [n, setN] = useState(5)
const [text, setText] = useState("")
const result = useMemo(() => factorial(n), [n])
const handleAdd = useCallback(() => setN((c) => c + 1), [])
return (
<div>
<p>{n}! = {result}</p>
<Child onAdd={handleAdd} />
<input value={text} onChange={(e) => setText(e.target.value)} />
</div>
)
}
useCallback(fn, [])mengembalikan function yang sama persis di tiap render, selama dependency-nya gak berubah. Dependency kosong berarti selalu function yang sama.setN((c) => c + 1)memakai bentuk function: React yang memberi nilainterbaru, jadihandleAddgak perlu membacan, dan dependency-nya boleh kosong.- Ketik di kolom teks:
Apprender ulang,handleAddtetap function yang sama,memomendapati semua props-nya sama, danChilddilewati.Child renderedgak tercetak lagi. useCallback(fn, deps)sama denganuseMemo(() => fn, deps). Bedanya cuma apa yang diingat:useMemomengingat nilai hasil function-nya,useCallbackmengingat function-nya sendiri.
Child dibungkus memo? |
handleAdd pakai useCallback? |
Saat kamu mengetik |
|---|---|---|
| tidak | tidak | Child render ulang |
| tidak | ya | Child render ulang, useCallback-nya sia-sia |
| ya | tidak | Child render ulang, karena props-nya function baru |
| ya | ya | Child dilewati |
Kapan gak perlu
- Hitungan murah.
count * 2lebih cepat dihitung langsung daripada diingat.useMemopunya biaya sendiri: menyimpan hasil dan membandingkan dependency di tiap render. - Child yang gak dibungkus
memo.useCallbackgak ada efeknya, karena child itu tetap render ulang bersama parent-nya. - Belum ada yang terasa lambat. Pakai setelah ada masalah nyata, misalnya terlihat di React DevTools Profiler, bukan di semua tempat.
- Project dengan React Compiler. React Compiler adalah alat build resmi dari tim React (stabil sejak 2025, paling pas dengan React 19) yang menambahkan memo seperti ini secara otomatis. Di project yang memakainya,
useMemodanuseCallbackmanual jarang perlu ditulis. Banyak project belum memakainya, dan editor latihan di sini juga tanpa compiler, jadi cara kerjanya tetap perlu kamu pahami.
⚠️ Jebakan yang sering ketemu
- Lupa dependency array.
useMemo(() => factorial(n))tanpa array dihitung ulang di tiap render, sama saja dengan tanpauseMemo. - Dependency yang kurang.
useMemo(() => factorial(n), [])mengingat hasil pertama selamanya:nberubah, hasilnya tetap 120. Masukkan semua nilai yang dibaca di dalamnya. - Membaca state lama di
useCallback.useCallback(() => setN(n + 1), [])selalu memakaindari render pertama, jadinmentok di 6. PakaisetN((c) => c + 1), atau masukkan[n]ke dependency. - Side effect di
useMemo. Fetch atau timer bukan hitungan. Tempatnya diuseEffect.
TL;DR: Tiap render menjalankan ulang semua kode di komponen. useMemo(() => hitung(n), [n]) mengingat nilai hasil hitungan mahal dan baru menghitung ulang kalau dependency-nya berubah. useCallback(fn, deps) mengingat function-nya sendiri, sama dengan useMemo(() => fn, deps), dan cuma berguna kalau function itu dikirim ke child yang dibungkus memo. Tulis dependency-nya lengkap, dan pakai setN((c) => c + 1) supaya useCallback gak membaca state lama. Hitungan murah dan child tanpa memo gak butuh keduanya. Di project dengan React Compiler, memo seperti ini ditambahkan otomatis.