1. Mengapa Komponen React Melakukan Re-render?
Secara default, saat sebuah komponen induk (Parent) melakukan re-render karena perubahan state, **seluruh komponen anak (Children) di bawahnya akan ikut di-render ulang**, terlepas dari apakah props anak tersebut berubah atau tidak. Pada kebanyakan aplikasi kecil, re-render ini sangat cepat dan tidak bermasalah. Namun, pada pohon komponen besar atau kalkulasi data berat (seperti filter ribuan baris data), re-render berulang dapat menyebabkan lag pada antarmuka.
2. React.memo: Memoizing Komponen Presentasional
`React.memo` adalah Higher-Order Component (HOC) yang memeriksa apakah `props` komponen berubah menggunakan perbandingan dangkal (*shallow comparison* `===`). Jika props tidak berubah sejak render sebelumnya, React akan melompati proses render komponen tersebut.
| 1 | import React from 'react'; |
| 2 | |
| 3 | const ExpensiveListItem = React.memo(function ExpensiveListItem({ title, onSelect }) { |
| 4 | console.log('Render:', title); |
| 5 | return <li onClick={onSelect}>{title}</li>; |
| 6 | }); |
| 7 | |
| 8 | export default ExpensiveListItem; |
3. useMemo: Caching Hasil Komputasi Berat
`useMemo` menyimpan hasil eksekusi sebuah fungsi kalkulasi. Fungsi hanya akan dijalankan ulang ketika salah satu dependency di dalam array berubah.
| 1 | import { useState, useMemo } from 'react'; |
| 2 | |
| 3 | function AnalyticsDashboard({ transactions }) { |
| 4 | const [filterType, setFilterType] = useState('ALL'); |
| 5 | |
| 6 | // Hanya dihitung ulang jika `transactions` atau `filterType` berubah |
| 7 | const totalVolume = useMemo(() => { |
| 8 | console.log('Menghitung total transaksi berat...'); |
| 9 | return transactions |
| 10 | .filter(t => filterType === 'ALL' || t.type === filterType) |
| 11 | .reduce((sum, t) => sum + t.amount, 0); |
| 12 | }, [transactions, filterType]); |
| 13 | |
| 14 | return <div>Total Volume: Rp {totalVolume.toLocaleString()}</div>; |
| 15 | } |
4. useCallback: Menjaga Stabilitas Referensi Fungsi
Di JavaScript, setiap kali sebuah fungsi di-deklarasikan ulang pada saat render, fungsi tersebut memiliki alamat memori baru (`() => {} !== () => {}`). Jika fungsi ini dipassing sebagai prop ke komponen yang dibungkus `React.memo`, memoization akan bocor (gagal) karena props dianggap selalu berubah. `useCallback` memastikan referensi fungsi tetap sama persis di antara render.
| 1 | import { useState, useCallback } from 'react'; |
| 2 | |
| 3 | function ProductCatalog() { |
| 4 | const [count, setCount] = useState(0); |
| 5 | |
| 6 | // handleDelete tidak akan dibuat ulang saat 'count' berubah |
| 7 | const handleDelete = useCallback((id) => { |
| 8 | console.log('Menghapus item:', id); |
| 9 | }, []); |
| 10 | |
| 11 | return ( |
| 12 | <div> |
| 13 | <button onClick={() => setCount(c => c + 1)}>Counter: {count}</button> |
| 14 | <ExpensiveListItem title="Item 1" onSelect={handleDelete} /> |
| 15 | </div> |
| 16 | ); |
| 17 | } |