Episode ini membahas debugging Redux dengan Redux DevTools: time-travel debugging, action trace, state diff, dan jump-to-state, lalu cara memecahkan masalah umum seperti selector yang membuat objek baru, mutasi di luar reducer, stale state, dan error hydration.

Keunggulan paling dikenal Redux adalah debuggability: setiap perubahan state bisa ditelusuri, dibandingkan, dan diputar ulang. Episode 19 mengajarkan cara memanfaatkan Redux DevTools secara penuh, lalu menutup dengan troubleshooting masalah yang paling sering ditemui pengembang Redux di lapangan.
Redux Toolkit memasang Redux DevTools secara otomatis lewat configureStore. Alat ini bukan sekadar melihat state — dengan time-travel, action trace, dan state diff, kalian bisa menjawab tiga pertanyaan kunci saat debugging: kapan state berubah, apa yang menyebabkannya, dan apa saja yang ikut berubah.
DevTools menyimpan seluruh histori action. Tombol jump memutar kembali state ke titik sebelum sebuah action dijalankan:
npm ls @reduxjs/toolkit react-reduxLangkah praktis:
Time-travel sangat berguna untuk mereproduksi bug: kalian tidak perlu menebak urutan interaksi yang memicu error — cukup putar balik dan ulangi.
Tab Trace menampilkan stack trace asal action: fungsi mana yang memanggil dispatch. Tab Diff memperlihatkan perbedaan state sebelum dan sesudah action:
{
"changeType": "UPDATE",
"stateAfter": {
"auth": { "user": { "id": 3, "name": "Budi" } },
"posts": { "status": "succeeded", "items": [ { "id": 1 } ] }
}
}Diff membantu menemukan perubahan tak terduga — misalnya sebuah action seharusnya hanya mengubah satu field tetapi ternyata mengubah banyak field. Trace mempersempit pencarian ke kode yang melakukannya.
Tombol jump memungkinkan mengecek state pada titik mana pun tanpa harus mengulang interaksi. Ini cara tercepat untuk membuktikan hipotesis: "apakah bug sudah ada sejak state X?". Bandingkan state sehat dengan state bermasalah, lalu cari action yang terakhir mengubah field yang relevan.
Gejala: komponen merender terus-menerus atau React menolak render karena update loop. Penyebab paling umum: selector mengembalikan objek/array baru setiap kali dipanggil.
const user = useAppSelector((state) => ({
name: state.user.name,
role: state.user.role,
}))Setiap action membuat objek { name, role } baru, sehingga useSelector selalu mendeteksi perubahan. Perbaikan: gunakan shallowEqual atau buat selector termemoize dengan createSelector:
import { shallowEqual } from "react-redux"
const user = useAppSelector(
(state) => ({
name: state.user.name,
role: state.user.role,
}),
shallowEqual,
)Aturan sederhana: kembalikan nilai primitif dari selector, atau gunakan shallowEqual untuk objek/array yang dibaca sebagian.
Redux Toolkit memakai Immer yang mendeteksi mutasi. Mutasi di luar reducer menghasilkan error:
Cannot produce a draft with an immutable valueKesalahan umum: memodifikasi state yang didapat dari getState() di thunk, atau menyimpan referensi state ke variabel lalu memutasi di tempat lain. Perbaikan: semua perubahan hanya terjadi di dalam reducer (atau via updateQueryData untuk cache RTK Query):
const users = store.getState().users.items
const updated = users.map((u) =>
u.id === 3 ? { ...u, role: "editor" } : u,
)
store.dispatch(usersLoaded(updated))map menghasilkan array baru tanpa menyentuh state asli. Immer kemudian menangani pembaruan draft di dalam reducer.
Masalah klasik: callback (seperti event handler atau setInterval) menangkap nilai state lama lewat closure:
const token = store.getState().auth.token
setTimeout(() => {
fetch("/api/data", {
headers: { Authorization: `Bearer ${token}` },
})
}, 5000)Jika login terjadi dalam 5 detik itu, token tetap nilai lama. Perbaikan: baca state langsung saat dibutuhkan dengan getState:
setTimeout(() => {
const token = store.getState().auth.token
fetch("/api/data", {
headers: { Authorization: `Bearer ${token}` },
})
}, 5000)Pola ini juga berlaku untuk listener middleware — gunakan listenerApi.getState() daripada meng-capture nilai lebih awal.
Error hydration muncul saat HTML server dan render client pertama tidak cocok. Sumber umum: tanggal atau random yang dihitung di render, dan state yang di-fetch dua kali dengan hasil berbeda:
export default function Clock() {
const [now, setNow] = useState<string | null>(null)
useEffect(() => setNow(new Date().toISOString()), [])
if (!now) return null
return <time>{now}</time>
}Konten yang bergantung pada nilai runtime dipindah ke useEffect sehingga server merender placeholder yang sama dengan client. Untuk state yang harus sama persis, hidrasi dari server seperti di episode 14.
Tip
Aktifkan opsi trace di DevTools saat ingin menelusuri asal action. Di production, jangan aktifkan — melacak stack trace setiap action menambah overhead yang tidak perlu.
Redux DevTools adalah superpower debugging: time-travel memutar ulang kejadian, action trace menemukan asal perubahan, dan state diff memperlihatkan cakupan perubahan. Dipadukan dengan pemahaman penyebab umum — selector yang membuat objek baru, mutasi di luar reducer, closure yang basi, dan hydration mismatch — kalian bisa mempersempit bug jauh lebih cepat daripada membaca log acak.
Inti yang harus dibawa pulang:
dispatch.shallowEqual.getState saat dibutuhkan untuk menghindari closure basi.Episode 20 berikutnya membahas fitur stabil terbaru RTK 2.x — kalian akan mengeksplorasi dukungan ESM dan TypeScript, Immer 10 dengan peningkatan performa, skills files untuk AI tooling, serta catatan rilis v2.11 hingga v2.12.0 tahun 2026.