Episode ini membahas selector sebagai kunci re-render minimal, memakai useShallow dari zustand/react/shallow untuk memilih beberapa slice sekaligus, dan subscription manual dengan subscribeWithSelector untuk log, analitik, dan sinkronisasi di luar React.

Selector adalah jantung re-render selektif Zustand — kemampuan membaca sebagian state tanpa memicu render saat bagian lain berubah. Episode 5 membahasnya secara tuntas: pola selector tunggal, jebakan selector yang mengembalikan object baru, solusi useShallow, dan subscription manual untuk kebutuhan di luar React.
Kalian akan memahami kapan selector dipakai di hook dan kapan subscription manual lebih tepat. Ini juga fondasi performa yang akan kita optimalkan lebih lanjut di episode 13.
Bentuk paling sederhana dan paling efisien — satu nilai primitif per pemanggilan:
const count = useCounter((s) => s.count)
const name = useProfile((s) => s.profile.name)useCounter((s) => s.count) mengembalikan primitif. Karena primitif dibandingkan dengan ===, komponen hanya re-render saat nilainya benar-benar berubah. Ini pola yang paling dianjurkan.
Jika selector mengembalikan object yang dibangun setiap render, hasilnya selalu dianggap berbeda:
// BURUK: object baru setiap render menyebabkan infinite re-render
const { count, status } = useCounter((s) => ({ count: s.count, status: s.status }))useCounter((s) => ({ count: s.count, status: s.status })) membuat object baru setiap kali dipanggil, sehingga strict equality selalu gagal dan komponen re-render tanpa henti. Solusinya: panggil selector terpisah, atau pakai useShallow.
useShallow melakukan perbandingan dangkal — membandingkan setiap field satu per satu, bukan referensi object:
import { useShallow } from 'zustand/react/shallow'
const { count, status } = useCounter(
useShallow((s) => ({ count: s.count, status: s.status })),
)useShallow((s) => ({ count: s.count, status: s.status })) diimpor dari zustand/react/shallow. Komponen re-render hanya ketika count atau status berubah. Catatan: impor di v5 berada di zustand/react/shallow, bukan lagi zustand/shallow.
Selector terpisah → paling efisien, kode lebih panjang
useShallow → nyaman, beberapa slice, perbandingan dangkalUntuk dua hingga tiga slice, keduanya setara. Pilih useShallow saat jumlah slice mulai banyak dan kode selector terpisah terasa berulang.
Secara default subscribe(listener) hanya menerima satu listener. Untuk subscribe dengan selector dan listener yang menerima dua argumen, aktifkan middleware subscribeWithSelector:
import { create } from 'zustand'
import { subscribeWithSelector } from 'zustand/middleware'
export const useUserStore = create(
subscribeWithSelector((set) => ({
name: '',
role: 'viewer',
setName: (name) => set({ name }),
})),
)Dengan middleware aktif, subscribe(selector, listener) tersedia:
const unsub = useUserStore.subscribe(
(s) => s.role,
(role, prevRole) => {
console.log(`role berubah: ${prevRole} → ${role}`)
},
)useUserStore.subscribe((s) => s.role, listener) memanggil listener hanya ketika s.role berubah, dan fungsi unsub menghentikan subscription. Panggil unsub di cleanup useEffect agar tidak bocor.
Subscription manual cocok untuk efek samping global yang tidak memerlukan render:
const unsub = useUserStore.subscribe(
(s) => s.name,
(name) => analytics.track('user_name_updated', { name }),
)Pola ini dipakai untuk sinkronisasi native, pencatatan log, atau memanggil API saat slice tertentu berubah — semua tanpa memicu render komponen. analytics.track(...) di sini adalah placeholder layanan analitik kalian.
Buat selector sebagai fungsi terpisah agar bisa diuji dan dipakai ulang:
export const selectCount = (s: CounterState) => s.count
const count = useCounter(selectCount)Selector bernama selectCount meningkatkan keterbacaan dan memudahkan unit testing — pola yang kembali dipakai di episode 18.
Episode 5 mengunci pemahaman selector dan subscription: selector tunggal untuk re-render minimal, useShallow untuk beberapa slice dengan perbandingan dangkal, dan subscribeWithSelector untuk efek samping di luar React.
Inti yang harus dibawa pulang:
Di episode 6 selanjutnya kita akan membahas actions dan async state — menempatkan logika di dalam store, action async dengan async/await, pengelolaan status loading/error/success, dan error handling yang terpusat. State aplikasi nyata hampir selalu asinkron.