Dua puluh item dalam sebuah feed terasa sempurna. Scroll-nya sangat mulus. Klien Anda senang. Lalu Anda merilisnya ke produksi, data masuk, dan tiba-tiba Anda menatap dua ribu baris. UI mulai tersendat. Penggunaan memori merangkak naik hingga OS mematikan aplikasi. Dalam keputusasaan, beberapa pengembang membungkus semuanya dalam ScrollView dan menganggap masalah selesai. Keputusan itu biasanya melahirkan tiga bug baru untuk setiap satu bug yang diselesaikan.

List adalah bottleneck performa yang menentukan bagaimana pengguna merasakan aplikasi React Native Anda. Jika Anda menanganinya dengan benar, aplikasi akan terasa native. Jika salah, layar yang paling indah sekalipun akan terasa lambat. Penyebab utamanya biasanya adalah ketidakcocokan antara komponen yang Anda pilih dengan beban kerja yang Anda berikan pada JS thread dan UI thread. React Native berjalan pada dua jalur. Logika Anda berada di JS thread, sementara proses rendering (painting) terjadi di native UI thread. Saat Anda merender list besar secara tidak tepat, kedua thread tersebut mulai kewalahan dengan kalkulasi layout, re-render, dan alokasi memori. Hasilnya adalah frame yang hilang (dropped frames), layar putih yang berkedip, dan akhirnya crash.

Pilih Alat yang Tepat

Memilih komponen list harus menjadi keputusan arsitektural yang disengaja, bukan sekadar refleks.

ScrollView adalah opsi termudah. Ia mengambil setiap child yang Anda berikan, langsung memasang (mount) setiap item ke dalam memori, dan menyerahkan seluruh tumpukan tersebut ke mesin scroll native. Itulah yang Anda butuhkan untuk konten pendek dan tetap seperti layar pengaturan, formulir login, atau halaman detail produk statis dengan sepuluh bagian. Ia mudah diprediksi dan mudah diberi gaya (style). Masalahnya adalah tidak ada virtualisasi. Jika Anda memberinya dua ribu item, ia akan dengan patuh membuat dua ribu native view. Jangan gunakan ScrollView untuk kumpulan data yang besar atau dinamis. Anggaplah ia sebagai poster berbingkai, bukan rak perpustakaan.

FlatList adalah pekerja keras untuk feed yang panjang dan seragam. Ia melakukan virtualisasi konten, yang berarti ia hanya memasang baris yang saat ini terlihat atau berada di dekat viewport. Saat pengguna melakukan scroll, FlatList melepas (unmount) sel yang keluar dari layar dan mendaur ulangnya untuk data yang baru masuk. Hal ini menjaga penggunaan memori tetap stabil tidak peduli seberapa besar array Anda tumbuh. Jika Anda membangun timeline sosial, pusat notifikasi, atau koleksi kartu serupa yang terus bergulir, FlatList adalah pilihan default yang tepat.

SectionList adalah FlatList dengan kemampuan pengorganisasian. Gunakan ini saat data Anda datang dalam kelompok-kelompok, seperti buku alamat yang diurutkan secara alfabetis, log latihan yang dibagi berdasarkan tanggal, atau daftar invoice yang disusun berdasarkan bulan. Ia merender sticky section header dan menangani logika pengelompokan untuk Anda. Di balik layar, ia menggunakan mesin virtualisasi yang sama dengan FlatList, sehingga Anda mendapatkan manfaat memori yang identik dengan tambahan struktur partisi berlabel.

FlashList hadir saat Anda perlu memeras setiap frame terakhir dari perangkat. Dibangun di atas ekosistem RecyclerListView, ia mendaur ulang view secara lebih agresif daripada FlatList dan bertujuan untuk mempertahankan enam puluh frame per detik bahkan pada perangkat kelas menengah. Jika Anda membangun antarmuka chat bervolume tinggi, katalog produk dengan kecepatan scroll yang cepat, atau layar apa pun di mana kelancaran adalah keunggulan kompetitif, FlashList layak untuk ditambahkan sebagai dependensi. Ini tidak diperlukan untuk setiap layar, tetapi untuk feed yang menentukan pengalaman inti, perbedaan performanya sangat terasa.

Pembunuh Performa yang Umum

Ada tiga tersangka utama saat sebuah list mulai terasa lambat.

Memasang (mounting) terlalu banyak React tree sekaligus adalah kegagalan yang paling dramatis. Ketika setiap baris adalah tree komponen yang kompleks, render awal dapat memblokir JS thread cukup lama hingga menghasilkan layar putih kosong atau delayed first paint yang buruk. Pengguna membuka aplikasi dan menunggu. Bahkan setelah pemuatan awal, baris yang berat membuat inisialisasi scroll menjadi lamban karena beberapa frame pertama habis digunakan untuk pekerjaan setup.

Terlalu banyak pekerjaan per frame muncul sebagai stuttering (tersendat) saat scroll. Anda memiliki anggaran sekitar enam belas milidetik per frame untuk menjaga animasi tetap mulus. Jika komponen baris menjalankan kalkulasi yang berat, melakukan parsing tanggal secara langsung, atau melakukan perbandingan objek yang mendalam di dalam render, Anda akan melampaui anggaran tersebut. UI thread akan kehilangan frame dan pengguna akan merasakan sentakan.

Penggunaan memori yang berlebihan adalah pembunuh senyap. Setiap native view memakan RAM. Tambahkan gambar besar yang tidak dioptimalkan, drop shadow pada setiap kartu, atau touchables yang bersarang (nested), maka penggunaan memori akan berlipat ganda. Di iOS, sistem dapat menghentikan aplikasi Anda tanpa peringatan. Di Android, pengguna akan melihat lag yang menumpuk hingga aplikasi terasa tidak dapat digunakan.

Daftar Periksa Optimasi

Small strategic habits separate a list that merely works from one that flies.

Use stable keys. Always pass a real identifier from your data set to the key prop. Never use the array index. If your list reorders, filters, or appends items, an index-based key tricks React into pairing the wrong data with the wrong recycled component. That mistake triggers unnecessary unmounts, state mismatches, and cascading re-renders. A proper ID tells React exactly which row moved where.

Memoize rows. Wrap your row component in React.memo so that it only re-renders when its props actually change. Without this guard, any parent state update can trigger a render pass across every visible row, even if their data is identical. In a long list that churns, those wasted cycles add up fast.

Keep renderItem stable. Avoid defining a new function directly inside the renderItem prop on every parent render. An inline arrow function like renderItem={({ item }) => <Row data={item} />} creates a new reference each time the parent updates. FlatList sees a changed prop and recycles the row unnecessarily. Define the render function outside the component or memoize it with useCallback so the reference stays stable.

Use getItemLayout whenever possible. If your rows have a fixed or predictable height, tell FlatList exactly what it is. This prop allows the list to skip expensive native measurement calls. Instead of measuring each cell after mount, the list calculates position mathematically. The difference is especially sharp on lists with hundreds or thousands of items, where onLayout chatter can bring the JS thread to its knees.

Optimize images aggressively. Unbounded images are list poison. Always set explicit width and height so the native layer reserves space before the image decodes. For remote images, use a caching library such as Expo Image or an equivalent that handles memory caching, disk persistence, and format optimization. The default React Native Image component works for prototypes, but production feeds need more control over memory and loading states.

Avoid nesting scroll containers. Never place a vertical FlatList inside a vertical ScrollView. The parent ScrollView captures all scroll events and disrupts the child FlatList's ability to measure its viewport. Virtualization breaks because FlatList no longer knows which rows should be visible. The result is that every row mounts anyway, defeating the entire purpose of virtualization. If you need a header above a list, use FlatList's own ListHeaderComponent prop. If you need complex sticky behavior, use a SectionList or FlashList with the appropriate header configuration.

The Golden Rule

If the content is small and finite, let ScrollView handle it. If the content grows with user-generated data or remote pagination, use a virtualized list. When the list is the centerpiece of the app and users will scroll for minutes at a time, reach for FlashList.

Here is one last truth that is easy to forget. Boring rows scroll fast. The lighter your row component, the smoother your list. Strip away nested navigations, heavy computations, and gratuitous animations from the individual row. Keep the markup flat, the logic thin, and the images sized. A list lives or dies by the cumulative weight of what it renders. Make each row cheap, and the list will feel expensive in the best possible way.