Optimasi Re-render React: useMemo, useCallback, dan React.memo dalam Praktik
1. Pendahuluan
Pernahkah Anda merasa aplikasi React yang Anda bangun mulai terasa lambat atau kurang responsif seiring bertambahnya fitur? Seringkali, akar masalahnya bukan pada kode yang buruk secara fundamental, melainkan pada re-render yang tidak perlu. Re-render adalah proses ketika React “menggambar ulang” komponen Anda ke DOM. Meskipun React sangat efisien, re-render yang berlebihan, terutama pada komponen yang kompleks atau sering berubah, bisa memakan banyak sumber daya dan menurunkan performa aplikasi.
Bayangkan Anda memiliki sebuah rumah yang catnya perlu diperbarui. Setiap kali ada perubahan kecil (misalnya, ada noda di dinding), Anda tidak perlu mengecat ulang seluruh rumah, bukan? Cukup bagian yang bernoda saja. Nah, di React, terkadang komponen kita “mengecat ulang” dirinya sendiri dan semua anak-anaknya (child components) padahal tidak ada perubahan yang signifikan. Inilah yang disebut re-render yang tidak perlu.
Artikel ini akan membawa Anda menyelami tiga senjata utama dalam toolbox React untuk mengoptimalkan re-render: useMemo, useCallback, dan React.memo. Kita akan membahas kapan dan bagaimana menggunakannya secara efektif, serta kapan sebaiknya tidak menggunakannya untuk menghindari premature optimization. Mari kita bangun aplikasi React yang lebih cepat dan mulus!
2. Memahami Re-render di React: Mengapa dan Kapan Terjadi?
Sebelum kita masuk ke solusi, penting untuk memahami masalahnya. Sebuah komponen React akan mengalami re-render ketika salah satu kondisi berikut terpenuhi:
- State-nya berubah: Ini adalah alasan paling umum. Ketika
useStateatauuseReducermengubah nilai state, komponen akan re-render. - Props-nya berubah: Jika komponen menerima props dari parent-nya, dan nilai props tersebut berubah, komponen akan re-render.
- Parent-nya re-render: Ini adalah poin krusial. Jika komponen parent re-render, secara default, semua child components-nya juga akan re-render, terlepas apakah props yang mereka terima benar-benar berubah atau tidak.
- Context yang digunakannya berubah: Jika komponen menggunakan React Context, dan nilai context tersebut berubah, komponen akan re-render.
Poin ketiga adalah penyebab utama re-render yang tidak perlu. Sebuah komponen Child mungkin menerima props yang sama persis dari Parent, tetapi jika Parent re-render (karena state-nya berubah), Child juga akan re-render. Jika Child ini memiliki logika komputasi yang berat atau memiliki banyak child components lagi, efeknya bisa berantai dan signifikan.
🎯 Tujuan Optimasi: Mencegah komponen re-render jika props atau state yang relevan dengannya tidak berubah.
3. useMemo: Mengoptimalkan Nilai yang Dikomputasi
useMemo adalah hook yang digunakan untuk memoize (menghafal) hasil komputasi suatu nilai. Ini berarti React hanya akan menghitung ulang nilai tersebut jika salah satu dependencies (ketergantungan) yang Anda tentukan berubah.
Kapan menggunakan useMemo?
- Ketika Anda memiliki komputasi yang mahal (misalnya, filter array besar, transformasi data kompleks, perhitungan matematis) yang menghasilkan sebuah nilai (angka, string, objek, array).
- Ketika Anda membuat objek atau array baru di setiap re-render, yang kemudian diteruskan sebagai props ke child component yang di-memoize (misalnya dengan
React.memo). Objek atau array baru akan dianggap “berubah” oleh React, meskipun isinya sama.
Contoh Praktis:
import React, { useState, useMemo } from 'react';
function ItemList({ items, filterText }) {
// Ini adalah komputasi yang mungkin mahal jika 'items' sangat besar
// dan 'filterText' sering berubah.
const filteredItems = useMemo(() => {
console.log('Menghitung ulang item yang difilter...');
return items.filter(item =>
item.name.toLowerCase().includes(filterText.toLowerCase())
);
}, [items, filterText]); // ✅ Hanya re-calculate jika items atau filterText berubah
return (
<div>
<h3>Daftar Item</h3>
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
function App() {
const [search, setSearch] = useState('');
const [data] = useState([
{ id: 1, name: 'Apel' },
{ id: 2, name: 'Jeruk' },
{ id: 3, name: 'Pisang' },
{ id: 4, name: 'Mangga' },
]);
return (
<div>
<input
type="text"
placeholder="Cari item..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<ItemList items={data} filterText={search} />
</div>
);
}
export default App;
Dalam contoh di atas, filteredItems hanya akan dihitung ulang jika items atau filterText berubah. Jika ada state lain di App yang berubah dan menyebabkan App re-render (misalnya, sebuah counter), ItemList akan re-render, tetapi fungsi filter tidak akan dijalankan lagi karena items dan filterText belum berubah.
⚠️ Penting: useMemo menerima sebuah fungsi (disebut “memoized function”) dan array dependencies. Fungsi ini akan dijalankan saat render pertama dan setiap kali salah satu dependency berubah.
4. useCallback: Mengoptimalkan Fungsi (Event Handlers)
Mirip dengan useMemo, useCallback digunakan untuk memoize sebuah fungsi. Ini sangat berguna ketika Anda meneruskan fungsi (seperti event handler) sebagai props ke child component.
Kapan menggunakan useCallback?
- Ketika Anda meneruskan fungsi sebagai props ke child component, terutama jika child component tersebut di-memoize dengan
React.memo. TanpauseCallback, setiap re-render parent akan membuat instance fungsi baru, yang akan dianggap sebagai “perubahan props” oleh child component, memicu re-render yang tidak perlu pada child. - Untuk mencegah loop tak terbatas (infinite loop) pada
useEffectatauuseLayoutEffectjika fungsi tersebut adalah dependency.
Contoh Praktis:
import React, { useState, useCallback, memo } from 'react';
// ✅ Komponen Child yang di-memoize
const ButtonSaya = memo(({ onClick, label }) => {
console.log(`ButtonSaya "${label}" di-render`);
return <button onClick={onClick}>{label}</button>;
});
function App() {
const [count, setCount] = useState(0);
const [message, setMessage] = useState('Halo');
// ❌ Tanpa useCallback, handleIncrement akan dibuat ulang setiap App re-render.
// Ini akan menyebabkan ButtonSaya re-render meskipun props lain tidak berubah.
// const handleIncrement = () => {
// setCount(prevCount => prevCount + 1);
// };
// ✅ Dengan useCallback, handleIncrement hanya dibuat ulang jika 'count' berubah
// (atau jika tidak ada dependencies, maka hanya sekali saat mount).
// Dalam kasus ini, kita tidak ingin setCount membuat re-render Button,
// jadi kita bisa pakai functional update untuk setCount.
const handleIncrement = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []); // Dependencies kosong berarti fungsi ini hanya dibuat sekali
const handleChangeMessage = useCallback(() => {
setMessage('Pesan baru!');
}, []); // Dependencies kosong
console.log('App di-render');
return (
<div>
<h1>Counter: {count}</h1>
<p>Pesan: {message}</p>
<ButtonSaya onClick={handleIncrement} label="Tambah Counter" />
<ButtonSaya onClick={handleChangeMessage} label="Ubah Pesan" />
<button onClick={() => console.log('Klik saya!')}>Log Saja</button>
</div>
);
}
export default App;
Dalam contoh ini, ButtonSaya di-memoize dengan React.memo. Jika handleIncrement dan handleChangeMessage tidak di-useCallback, setiap kali App re-render (misalnya karena count berubah), fungsi-fungsi tersebut akan dibuat ulang. Ini akan menyebabkan ButtonSaya re-render karena props onClick dianggap berubah, padahal ButtonSaya tidak peduli dengan perubahan count atau message. Dengan useCallback, fungsi handleIncrement dan handleChangeMessage hanya dibuat sekali dan tetap sama di setiap re-render App, sehingga ButtonSaya tidak perlu re-render.
💡 Tips: Untuk setCount di dalam useCallback, gunakan functional update (setCount(prevCount => prevCount + 1)) agar tidak perlu memasukkan count ke dalam array dependencies useCallback. Ini membuat fungsi tersebut tetap stabil dan tidak sering dibuat ulang.
5. React.memo: Mengoptimalkan Komponen secara Keseluruhan
React.memo adalah Higher-Order Component (HOC) yang membungkus komponen fungsional Anda. Ini adalah cara React untuk melakukan shallow comparison pada props komponen. Jika props yang diterima komponen tidak berubah, React.memo akan mencegah komponen tersebut untuk re-render.
Kapan menggunakan React.memo?
- Ketika komponen Anda “pure” dalam artian, ia akan selalu merender output yang sama jika diberikan props yang sama.
- Ketika komponen Anda menerima props yang sering berubah, tetapi Anda tahu bahwa perubahan tersebut tidak selalu memerlukan re-render visual.
- Ketika komponen Anda adalah child dari komponen parent yang sering re-render, tetapi komponen child itu sendiri tidak terlalu sering menerima props yang berbeda.
Contoh Praktis:
Lihat kembali contoh ButtonSaya di bagian useCallback. Kita membungkusnya dengan memo:
// ✅ Komponen Child yang di-memoize
const ButtonSaya = memo(({ onClick, label }) => {
console.log(`ButtonSaya "${label}" di-render`);
return <button onClick={onClick}>{label}</button>;
});
Tanpa memo, ButtonSaya akan re-render setiap kali App re-render. Dengan memo, ButtonSaya hanya akan re-render jika props onClick atau label benar-benar berubah (secara shallow comparison). Karena onClick sekarang di-useCallback dengan dependencies kosong, ButtonSaya tidak akan re-render kecuali label berubah.
Custom Comparison dengan arePropsEqual
Secara default, React.memo melakukan shallow comparison pada props. Jika Anda membutuhkan logika perbandingan yang lebih kompleks, Anda bisa menyediakan fungsi arePropsEqual sebagai argumen kedua:
const MyComplexComponent = memo((props) => {
// ... render logic
}, (prevProps, nextProps) => {
// ✅ Kembalikan true jika props sama (tidak perlu re-render)
// ✅ Kembalikan false jika props berbeda (perlu re-render)
return prevProps.value.id === nextProps.value.id &&
prevProps.data.length === nextProps.data.length;
});
Ini sangat berguna untuk props yang berupa objek atau array yang referensinya selalu baru, tetapi Anda hanya peduli pada beberapa properti di dalamnya.
6. Kapan Tidak Menggunakan Optimasi Ini (Hindari Premature Optimization)
Meskipun useMemo, useCallback, dan React.memo adalah alat yang ampuh, penggunaannya juga memiliki biaya:
- Overhead Memoization: React perlu menyimpan nilai memoized dan membandingkan dependencies pada setiap re-render. Untuk komputasi atau fungsi yang sangat sederhana, biaya memoization bisa lebih besar daripada manfaatnya.
- Kompleksitas Kode: Terlalu banyak memoization dapat membuat kode lebih sulit dibaca dan dipahami.
- Kesalahan Dependencies: Lupa menambahkan dependency yang benar ke
useMemoatauuseCallbackbisa menyebabkan bug aneh di mana nilai atau fungsi tidak diperbarui saat seharusnya.
❌ Kapan sebaiknya TIDAK menggunakannya:
- Komponen yang Sering Berubah: Jika komponen Anda memang sering re-render karena props atau state-nya memang sering berubah,
React.memotidak akan banyak membantu. - Komponen Sederhana: Untuk komponen yang sangat kecil dan ringan, biaya re-render-nya mungkin negligible. Overhead memoization bisa jadi lebih mahal.
- Fungsi atau Nilai Sederhana: Jika komputasi atau fungsi Anda sangat ringan dan tidak diteruskan ke child yang di-memoize,
useMemoatauuseCallbackmungkin tidak diperlukan.
📌 Aturan Emas: Jangan optimasi sebelum Anda tahu ada masalah. Gunakan React DevTools Profiler untuk mengidentifikasi komponen mana yang paling sering re-render atau paling lambat. Baru setelah itu, terapkan optimasi secara selektif.
Kesimpulan
useMemo, useCallback, dan React.memo adalah teknik memoization yang sangat berharga dalam pengembangan aplikasi React. Mereka memungkinkan Anda untuk mengontrol kapan komponen re-render, mengurangi beban kerja React, dan pada akhirnya meningkatkan performa dan responsivitas aplikasi Anda.
useMemountuk memoize nilai hasil komputasi yang mahal.useCallbackuntuk memoize fungsi yang diteruskan ke child component atau digunakan sebagai dependencyuseEffect.React.memountuk memoize komponen fungsional agar tidak re-render jika props-nya tidak berubah.
Ingatlah untuk selalu mengoptimasi dengan bijak. Gunakan alat profiler untuk menemukan bottleneck nyata, dan terapkan teknik ini secara strategis, bukan pada setiap komponen. Dengan pendekatan yang tepat, Anda bisa membangun aplikasi React yang tidak hanya fungsional, tetapi juga cepat dan menyenangkan bagi pengguna!
🔗 Baca Juga
- Menggali Lebih Dalam React Hooks: Panduan Praktis untuk Developer Modern
- Membangun Custom Hooks yang Kuat dan Reusable: Mengoptimalkan Logika dan State di Aplikasi React Anda
- Mempercepat Website Anda: Panduan Praktis Web Performance Optimization
- Menguasai Core Web Vitals: Strategi Praktis untuk Performa Web yang Unggul