Satu Baris yang Terlupa: Mapper DTO yang Menghancurkan Frontend
Field Status ada di entitas tapi hilang di mapper: API membalas string kosong, halaman edit crash. Satu baris plus regresi test menutupnya.
Ringkasan
Bug muncul karena field status lupa dipetakan di dto announcement jadi frontend dapet string kosong dan crash pas baca badge. Di backend ditambahin mapping yang hilang plus test regresi, di frontend dibikin helper toFormStatus yang cuma izinin published dan scheduled sisanya jadi draft. Sekalian navigasi diganti dari window location ke router push dan refresh biar toast nggak hilang.
Saya baru saja klik tombol simpan di halaman admin, lalu layar tiba-tiba putih. Di konsol browser, satu baris error merah menyala: Cannot read properties of undefined (reading 'badge'). Awalnya saya kira ini cuma salah ketik aja di komponen React. Mungkin nama prop-nya keliru, atau state-nya belum terinisialisasi dengan benar saat komponen dimuat pertama kali. Saya bahkan sempat ngecek ulang file informasi-editor.tsx berkali-kali, mencari tahu kenapa hal ini bisa terjadi.
String kosong ini sah sebagai JSON, jadi nggak ada yang komplain di backend. Di frontend, kunci "" nggak ada di objek STATUS_META [2] — lookup mengembalikan undefined, dan React crash pas membaca badge dari undefined itu.
Satu kolom yang nggak pernah dikirim
Ini contoh nyata dto status field drift. Field Status udah ada di entitas database. Modul entityadmin dan reviewadmin udah memetakannya dengan benar ke response. Tapi di modul announcementadmin, fungsi toResponse di file dto.go lupa menyertakan satu baris saja. Kompilasi Go tetap sukses tanpa error, JSON yang dihasilkan tetap valid secara struktur, tapi nilainya hampa. Mapper yang dibuat manual memang rentan terhadap drift seperti ini saat schema berkembang.
Ditutup dari dua sisi
Saya sadar kalau mengandalkan satu sisi saja itu berbahaya banget. Solusinya harus dua arah, baik di backend maupun frontend. Di backend, saya nambahin baris yang hilang itu dan langsung bikin regression test bernama TestToResponseCopiesStatus di service_test.go. Test ini ngecek tiga skenario secara eksplisit: draft, published, dan scheduled. Kalau ada field baru di masa depan yang lupa dipetakan, test ini akan gagal duluan sebelum kode sampai ke production.
Di frontend, saya nggak lagi percaya begitu saja pada nilai enum dari luar. Kode lama pakai (d.status as FormState["status"]) ?? "draft". Masalahnya, operator nullish coalescing ?? cuma nangkep null atau undefined, bukan string kosong "". Saya bikin helper function baru bernama toFormStatus(v) untuk mengatasi ini. Logikanya sederhana tapi tegas: lakukan whitelist hanya untuk nilai "published" atau "scheduled". Selain itu, semua nilai lain dipaksa jatuh ke "draft". Helper ini saya pakai di informasi-editor.tsx dan juga wisata-editor.tsx biar konsisten di seluruh aplikasi.
Pendekatan whitelist ini jauh lebih aman daripada nebak-nebak nilai yang mungkin datang dari API. Daripada biarin aplikasi crash karena nilai yang nggak terduga, lebih baik kita paksa masuk ke state default yang aman. Masalah dto status field drift ini mengajarkan saya untuk selalu memvalidasi batas atas data yang masuk, bukan cuma mengandalkan asumsi.
Sekalian: toast yang selamat
Pas lagi benerin bug ini, saya juga sadar ada masalah UX kecil yang perlu diperbaiki. Abis berhasil bikin informasi, halaman melakukan full reload pakai window.location.href [3]. Efek sampingnya, toast notifikasi "Informasi dibuat" muncul sekilas terus hilang karena halaman termuat ulang dari nol. Pengguna jadi nggak sempat baca konfirmasi tersebut.
Saya ngubah logika ini jadi kombinasi router.push dan router.refresh dari Next.js [1]. router.push nangani navigasi sisi klien dan nambah entri history, sementara router.refresh ngambil ulang data server dan menggabungkan payload RSC tanpa ngilangin state klien kayak useState atau posisi scroll. Hasilnya, transisi jadi mulus dan notifikasi toast tetap kelihatan jelas oleh pengguna.
Saya pribadi lebih milih nulis helper normalisasi eksplisit kayak toFormStatus daripada bergantung pada fitur bahasa yang keliatan pintar tapi rapuh kayak nullish coalescing buat data yang nggak terkontrol. Kepastian emang lebih berharga daripada keanggunan sintaks. Keputusan buat nambahin regression test di mapper DTO bukan sekadar formalitas. Itu jaring pengaman yang mastiin kesalahan sepele kayak satu baris kode yang terlupa nggak bakal lolos lagi ke tahap testing, apalagi production. Polanya juga nggak berhenti di kolom status: field enum lain kayak role atau bidang bisa pakai trik whitelist yang sama, dan mapper-nya tetap dijaga test regresi biar field baru nggak pernah lagi lolos kosong.
Sources
[1] https://nextjs.org/docs/app/api-reference/functions/use-router
[2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined
[3] https://developer.mozilla.org/en-US/docs/Web/API/Location/href