Episode ini membahas performa Redux: reselect dan createSelector untuk memoization, komposisi derived state, menghindari re-render berlebihan dengan shallowEqual, memecah state granular, serta teknik profiling menggunakan Redux DevTools.

Redux menawarkan performa yang baik secara default, tetapi cara kalian menulis selector sangat menentukan seberapa sering komponen merender ulang. Selector yang mengembalikan array atau objek baru setiap dipanggil akan membuat useSelector menganggap state berubah — padahal data sama saja. Episode 13 membahas cara mengendalikan hal ini dengan reselect dan createSelector.
Tujuannya sederhana: memastikan komponen hanya merender ketika data yang ia baca benar-benar berubah. Kita akan belajar memoization, komposisi selector, shallow equality, struktur state granular, dan cara memverifikasi semuanya dengan Redux DevTools.
Perhatikan selector yang memfilter setiap kali dipanggil:
import { createSelector } from "@reduxjs/toolkit"
export const selectVisiblePosts = createSelector(
[(state) => state.posts.items],
(items) => items.filter((post) => post.published),
)Tanpa createSelector, kode items.filter(...) menghasilkan array baru pada setiap pemanggilan. useSelector membandingkan hasil dengan ===; karena referensi berbeda, komponen merender ulang setiap kali ada action — bahkan yang tidak menyentuh posts sama sekali.
createSelector mengingat input terakhir dan hasilnya. Ia mengevaluasi fungsi kombinasi hanya jika salah satu input selector mengembalikan nilai berbeda:
import { createSelector } from "@reduxjs/toolkit"
const selectItems = (state) => state.posts.items
const selectKeyword = (state) => state.posts.keyword
export const selectFilteredPosts = createSelector(
[selectItems, selectKeyword],
(items, keyword) =>
items.filter((post) => post.title.toLowerCase().includes(keyword)),
)Selama items dan keyword tidak berubah, hasil filter di-cache dan direferensikan ulang. Komponen yang memakai selectFilteredPosts baru merender ketika keyword atau item benar-benar berganti.
Selector memoized bisa disusun bertingkat. Hasil satu selector menjadi input selector berikutnya, sehingga komputasi mahal tidak diulang:
import { createSelector } from "@reduxjs/toolkit"
const selectAllUsers = (state) => state.users.items
const selectActiveUserId = (state) => state.users.activeId
export const selectActiveUser = createSelector(
[selectAllUsers, selectActiveUserId],
(users, activeId) => users.find((u) => u.id === activeId),
)
export const selectActiveUserStats = createSelector(
[selectActiveUser],
(user) => {
if (!user) return null
return { postCount: user.postIds.length, followerCount: user.followers }
},
)selectActiveUserStats hanya dihitung ulang ketika selectActiveUser mengembalikan referensi baru — yaitu saat activeId atau daftar users berubah. Komposisi ini menjaga performa tetap linear terhadap jumlah data yang benar-benar berubah.
Semakin besar slice yang diambil selector, semakin banyak komponen yang ikut merender. Pilih hanya field yang dibutuhkan komponen:
import { useAppSelector } from "../../app/hooks"
export function UserName() {
const name = useAppSelector((state) => state.users.items[0]?.name)
return <p>{name}</p>
}useAppSelector membandingkan hasil dengan ===. Mengembalikan state.users.items[0]?.name (sebuah string) berarti komponen hanya merender ketika nama user pertama berubah — bukan setiap kali users lain bertambah.
Kadang selector harus mengembalikan objek atau array yang disusun dari beberapa field. shallowEqual membandingkan satu level properti sehingga perubahan pada satu kunci tidak memicu re-render hanya karena objeknya baru:
import { useAppSelector } from "../../app/hooks"
import { shallowEqual } from "react-redux"
export function ProfileHeader() {
const profile = useAppSelector(
(state) => ({
name: state.users.items[0]?.name,
avatar: state.users.items[0]?.avatar,
}),
shallowEqual,
)
return <header>{profile.name}</header>
}Tanpa shallowEqual, objek baru di setiap panggilan membuat komponen merender terus-menerus. Dengan shallowEqual, komponen hanya merender ketika name atau avatar benar-benar berubah nilainya.
Merancang state yang granular membantu selector bekerja lebih efisien. Simpan data yang berubah dengan frekuensi berbeda di kunci terpisah:
{
"posts": {
"items": [ ... ],
"editingId": null,
"filters": { "keyword": "", "sortBy": "newest" }
}
}Mengetik di kotak pencarian hanya mengubah filters.keyword, sehingga selector yang membaca posts.items tetap memakai hasil memoized. Membiarkan semua data dalam satu array besar akan membuat setiap perubahan menandai seluruh array berubah.
Redux DevTools menyediakan informasi yang berguna untuk menemukan sumber re-render:
npm view @reduxjs/toolkit versionPerhatikan tiga hal di tab Action:
Untuk memverifikasi hasil optimasi, beri log pada fungsi render:
export function PostList() {
const posts = useAppSelector(selectFilteredPosts)
console.log("PostList render", posts.length)
return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
}Jika console.log muncul saat action yang tidak relevan di-dispatch, berarti selector kalian belum termemoize. Jika muncul hanya saat data yang dibaca berubah, optimasi berhasil.
Warning
Memoization bukan sihir: hasil cache direset setiap kali input berubah, dan createSelector tetap memanggil input selectors pada setiap evaluasi. Jangan menghitung ulang sesuatu yang berat di dalam input selector itu sendiri.
Performansi Redux sebagian besar ditentukan oleh selector. createSelector menyediakan memoization untuk derived state, komposisi selector mencegah komputasi berulang, shallowEqual menangani koleksi yang dibaca sebagian, dan state granular mengurangi lingkup re-render. Dengan profil yang dibaca dari Redux DevTools, kalian bisa membuktikan setiap optimasi benar-benar bekerja.
Inti yang harus dibawa pulang:
createSelector meng-cache hasil hingga salah satu input selector berubah.shallowEqual membandingkan properti level satu untuk objek dan array.Episode 14 berikutnya membawa Redux ke server rendering — kalian akan membuat store per-request di Next.js App Router, memakai Provider untuk SSR, menghidrasi state, dan mengintegrasikan RTK Query dengan Server Components.