Episode ini membahas cara sistematis menemukan masalah performa dan bug di Zustand: profil re-render dengan React Profiler dan Redux DevTools, lalu troubleshooting umum seperti infinite render, state yang tidak ter-update, hydration mismatch, dan stale closure pada subscription.

Ketika aplikasi terasa lambat atau berperilaku aneh, kalian butuh cara sistematis menemukan akar masalahnya. Episode 19 membahas dua alat utama — React Profiler dan Redux DevTools — untuk melacak re-render dan update store, lalu menelusuri masalah yang paling sering muncul di Zustand: infinite render, state yang tidak ter-update, hydration mismatch, dan stale closure.
Setiap masalah kita bahas gejala, penyebab, dan solusinya supaya debugging tidak lagi menebak-nebak. Pola yang dipakai selalu sama: ukur dulu, lalu perbaiki berdasarkan data, bukan asumsi.
React Profiler di DevTools browser merekam render tiap komponen beserta durasinya. Jalankan aplikasi, lakukan interaksi, lalu periksa komponen mana yang render paling sering dan paling lambat. Store Zustand muncul sebagai hook di dalam komponen pemakainya — perhatikan komponen dengan selector yang mengembalikan object baru.
Untuk mengaktifkan profiling di production, render aplikasi dengan React profiling build:
npm run build -- --profilePerintah npm run build -- --profile menghasilkan production build dengan React Profiler aktif, berguna untuk mengukur performa nyata tanpa mode development.
Setelah build berjalan, buka React Profiler di DevTools dan rekam satu sesi interaksi. Filter komponen dengan total render tertinggi, lalu periksa penyebabnya satu per satu: props baru, state baru, atau re-render induk. Data ini jauh lebih akurat daripada menebak komponen mana yang bermasalah.
Dengan middleware devtools, setiap set muncul sebagai action di Redux DevTools. Panel diff memperlihatkan field yang berubah, dan tombol time-travel memundurkan state ke sebelum bug terjadi. Ini cara tercepat membuktikan bahwa update store adalah penyebab re-render beruntun.
Kombinasikan keduanya: React Profiler menunjukkan komponen mana yang render berlebihan, sementara Redux DevTools menunjukkan update store mana yang memicunya. Dengan dua sudut pandang ini, akar masalah hampir selalu terlihat.
Gejala paling umum: komponen me-render terus-menerus sampai aplikasi hang. Penyebabnya selector yang mengembalikan object atau array baru setiap render:
const { count } = useCounter((s) => ({ count: s.count }))(s) => ({ count: s.count }) membuat object baru setiap render, sehingga strict equality selalu gagal dan React me-render lagi. Solusinya pakai selector primitif atau useShallow. Kalau sudah pakai useShallow dan masih hang, periksa apakah action memicu set yang mengubah state yang sama di dalam render loop.## Troubleshooting: State Tidak Ter-update
Gejala kedua: UI tidak berubah meski set sudah dipanggil. Penyebabnya biasanya mutasi state secara langsung sehingga referensi object tidak berubah. Zustand membandingkan dengan strict equality — object yang di-mutasi tetap object yang sama:
const setProfile = (name) =>
set((s) => {
s.user.name = name
return s
})s.user.name = name mengubah properti tanpa membuat object baru, sehingga React tidak melihat perubahan. Solusinya selalu kembalikan object baru:
const setProfile = (name) =>
set((s) => ({ user: { ...s.user, name } })){ ...s.user, name } menghasilkan referensi baru yang dideteksi strict equality. Bila object sangat bersarang, pertimbangkan middleware immer dari episode 9 untuk menulis draft yang mutable.
Untuk memverifikasi bahwa update terdeteksi, bandingkan referensi state sebelum dan sesudah set: getState().user harus menghasilkan object yang berbeda setelah set berhasil.
Error ini muncul saat render server berbeda dengan render client pertama — biasanya karena store membaca localStorage saat render. Solusinya sudah dibahas di episode 14: gunakan hook useHydrated dan jangan akses storage saat render server.
Jika error muncul di browser namun tidak di server, periksa juga apakah komponen menulis state saat render. Menulis state ke store di dalam render function memicu update yang tidak sinkron dan berpotensi mismatch — pindahkan penulisan ke dalam event handler atau effect.
Subscription yang menangkap variabel lama dari closure memakai nilai usang:
useEffect(() => {
return useAuthStore.subscribe((state) => {
if (state.token && state.user.role === 'admin') {
enableAdminPanel()
}
})
}, [])state.token dan state.user.role dibaca dari parameter subscribe, bukan dari closure luar, sehingga selalu terbaru. Aturan emas: di dalam listener, baca nilai dari state yang diteruskan subscribe, bukan dari variabel luar yang mungkin basi.
Alternatifnya, panggil getState di dalam listener agar selalu membaca nilai terbaru tanpa bergantung pada parameter:
useEffect(() => {
return useAuthStore.subscribe(() => {
const { token, user } = useAuthStore.getState()
if (token && user.role === 'admin') {
enableAdminPanel()
}
})
}, [])Contoh umum stale closure lainnya: menyimpan token dari render pertama ke variabel luar, lalu membacanya di dalam listener. Karena listener menangkap variabel lama, nilainya tidak pernah terbaru. Solusinya selalu baca dari parameter subscribe atau panggil getState di dalam listener.
Episode 19 melengkapi kotak alat debugging: React Profiler untuk merekam re-render, Redux DevTools untuk melacak update store, plus solusi untuk empat masalah umum — infinite render dari object baru, state yang tidak ter-update dari mutasi, hydration mismatch, dan stale closure pada subscription.
Sisakan waktu untuk memahami root cause daripada sekadar menimpa gejala — perbaikan yang benar muncul dari pengukuran yang benar.
Inti yang harus dibawa pulang:
Di episode 20 selanjutnya kita akan membahas fitur stabil terbaru Zustand v5 — fungsi useStore untuk React 19, perbaikan typing middleware, rangkaian rilis 2026 dari v5.0.10 sampai v5.0.14, dan cara memverifikasi versi terbaru di registry.