React Performance Patterns — Performance

React Performance Patterns React sangat cepat secara default, tapi aplikasi besar bisa mengalami masalah re-render berlebihan. Memahami kapan dan bagaimana…

React performance artinya bikin React melakukan kerja yang memang perlu saja: gak render ulang komponen yang hasilnya sama, dan gak membiarkan kerja berat menahan ketikan user. Bayangin kamu ganti satu kata di halaman 1 sebuah buku, lalu printer mencetak ulang seluruh 200 halaman. memo itu printer yang mengecek dulu: "isi halaman ini sama dengan kemarin? Lewati." Tapi cek itu juga makan waktu, jadi cuma layak untuk halaman yang mahal dicetak.

🧪 Tebakan cepat: ProductList sudah dibungkus memo, tapi App mengirim onSelect={() => pilih(id)} yang ditulis langsung di JSX. Kamu klik tombol +1 keranjang. ProductList render ulang atau gak? Tebak dulu, jawabannya ada di bagian "useCallback dan useMemo".

Kapan komponen render ulang

Render artinya React memanggil function komponenmu untuk menghitung tampilan, lalu mengubah DOM di bagian yang beda saja. Komponen render ulang kalau:

  1. state-nya sendiri berubah (useState, useReducer),
  2. parent-nya render ulang: semua child ikut render, walaupun props-nya sama persis,
  3. context yang dibacanya berubah.

"Props berubah" bukan pemicu tersendiri: props cuma bisa berubah karena parent render ulang. Render ulang biasanya murah (1 atau 2 ms). Jadi masalah kalau komponennya mahal, misalnya list 5.000 produk, dan render-nya sering.

Ukur dulu dengan React DevTools Profiler

Jangan menebak komponen mana yang lambat. Pasang ekstensi React DevTools, buka tab Profiler, tekan Record, lakukan interaksi yang terasa lambat, lalu Stop. Flamegraph-nya menunjukkan tiap komponen yang render dan berapa lama; yang abu-abu gak render. Di pengaturannya, nyalakan "Record why each component rendered" supaya tiap render ada alasannya.

Ini hasil dari Lab di halaman ini: toko dengan kotak cari, tombol +1 keranjang, dan ProductList berisi 5.000 produk. Satu render list makan sekitar 280 ms (di Lab disimulasikan, gak benar-benar membakar CPU).

Interaksi App render ProductList render Props ProductList berubah?
Ketik "k" 1 1 ya, query "" jadi "k"
Ketik "o" 1 1 ya, "k" jadi "ko"
Klik +1 keranjang 8 kali 8 8 tidak
Total 10 10 2 dari 10, yaitu 20%

Delapan render list itu sia-sia: 8 × 280 ms = 2.240 ms kerja tanpa hasil yang beda.

React.memo: lewati render kalau props sama

import { memo, useState } from 'react';

const ProductList = memo(function ProductList({ query }) {
  const visible = products.filter((p) => p.name.includes(query)); // 5.000 produk, mahal
  return (
    <ul>
      {visible.map((p) => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
});

function App() {
  const [query, setQuery] = useState('');
  const [cart, setCart] = useState(0);
  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={() => setCart(cart + 1)}>+1 keranjang ({cart})</button>
      <ProductList query={query} />
    </>
  );
}

useCallback dan useMemo: jaga props tetap sama

ProductList tetap render ulang. Itu jawaban tebakannya. () => pilih(id) yang ditulis langsung di JSX adalah function baru di setiap render, jadi memo melihat prop onSelect berubah. Sama juga dengan object atau array yang dibuat di dalam render.

const handleSelect = useCallback((id) => setSelected(id), []); // function yang sama antar render
const sorted = useMemo(() => [...products].sort(byPrice), [products]); // dihitung ulang kalau products berubah

<ProductList query={query} items={sorted} onSelect={handleSelect} />

Ringkasnya: React.memo untuk komponen, useMemo untuk nilai, useCallback untuk function.

Input tetap responsif: useDeferredValue dan useTransition

Tanpa trik, tiap ketikan ikut menunggu list 280 ms selesai render, jadi jeda dari ketik sampai huruf muncul sekitar 290 ms. Batas INP yang good itu 200 ms.

const deferredQuery = useDeferredValue(query);
<ProductList query={deferredQuery} /> // ProductList tetap memo

React sekarang render dua kali per ketikan. Render pertama mendesak: input dapat huruf baru, deferredQuery masih nilai lama, dan ProductList dilewati memo. Render kedua jalan di background dengan nilai baru, dan bisa disela kalau kamu mengetik lagi.

Mode di Lab List render per ketikan Jeda input
Tanpa apa-apa 1, di render mendesak sekitar 290 ms
useDeferredValue tanpa memo 2, nilai lama dan baru sekitar 290 ms
useDeferredValue + memo 1, di background sekitar 12 ms

Kalau kamu sendiri yang memanggil setState-nya, pakai useTransition: startTransition(() => setFilter(value)) menandai update itu gak mendesak, dan isPending bisa dipakai untuk indikator loading.

Pola lain yang sering menolong

Versi latihan: kapan memo menguntungkan

shouldMemoize mengembalikan true hanya kalau ketiga syarat terpenuhi: renderCount > 5 (sering render), propsChangedPercent < 30 (props jarang berubah, jadi memo sering bisa melewati), dan computationMs > 10 (tiap render mahal).

renderCount propsChangedPercent computationMs Hasil
10 20 15 true
3 20 15 false, jarang render
10 50 15 false, props sering berubah
10 20 5 false, render-nya murah

identifyUnneededReRenders menyaring komponen dengan propsChanged === false: yang render tanpa props berubah, calon React.memo. Dari Header, UserCard, dan Footer, hasilnya Header dan Footer.

⚠️ Jebakan yang sering ketemu

TL;DR: Komponen render ulang kalau state-nya berubah, parent-nya render ulang, atau context-nya berubah. Ukur dulu dengan React DevTools Profiler. React.memo melewati render kalau props sama, useCallback menjaga function tetap sama, useMemo menyimpan nilai. Function, object, atau array baru di setiap render membatalkan memo. useDeferredValue (atau useTransition) plus memo membuat input tetap responsif sementara list berat menyusul di background. Pakai key yang stabil, simpan state dekat pemakainya, virtualisasi list panjang, dan pertimbangkan React Compiler. Memo menguntungkan kalau komponen sering render, props-nya jarang berubah, dan render-nya mahal.

Yang akan kamu pelajari