Skip to content

Tiga Halaman 7 Baris, Satu Komponen 471 Baris

Adityo Guni Waluyo

Modul Kelembagaan di admin CMS: tiga route server 7-14 baris, satu editor klien 471 baris, dan guard id yang melempar 404 sebelum fetch.

Ringkasan

Halaman 404 muncul karena id organisasi tidak valid. Validasi id dilakukan di route sunting sebelum komponen dimuat. Komponen klien melakukan validasi tambahan untuk kenyamanan pengguna.

URL /admincms/organisasi/abc dibuka di browser. Yang muncul bukan form kosong atau daftar error validasi di dalam halaman, melainkan halaman 404 bawaan Next.js, dan request data tidak pernah berangkat dari browser. Perubahan itu adalah commit c50f35b, +908 baris di 7 file, yang menambahkan modul Kelembagaan pada admin CMS sebuah portal kota: daftar organisasi, form tambah, form sunting berisi editor organisasi admin lengkap dengan daftar anggota.

Tujuh Baris yang Berbuat Dua Hal

Modul ini punya tiga file route, masing-masing 7 sampai 14 baris. Dua di antaranya hanya menulis metadata halaman lalu merender satu komponen klien. File ketiga, route sunting, melakukan satu hal lagi sebelum komponen itu sempat dimuat:

export default function EditOrganisasiPage({ params }: { params: { id: string } }) {
  const id = Number(params.id);
  if (!Number.isInteger(id) || id <= 0) notFound();
  return <OrganisasiEditor id={id} />;
}

params.id datang sebagai string, dan mengubah "abc" lewat Number menghasilkan NaN, bukan error. notFound() sendiri melempar error yang merender halaman 404 Next.js dan menghentikan rendering route segment tempat ia dipanggil [2], sehingga komponen sunting tidak pernah sampai berjalan dengan id yang rusak.

Tebakan yang lebih sederhana sempat terpikir: cukup validasi id di dalam komponen sunting, di bagian yang sudah membaca state dari klien. Pilihan itu menaruh satu-satunya penjaga pada lapisan yang baru aktif setelah JavaScript termuat, dan pada route yang state-nya tidak ikut ter-reset saat navigasi. Dokumen autentikasi Next.js menyebut alasan strukturnya: layout tidak dirender ulang saat navigasi, jadi pemeriksaan sebaiknya ditaruh dekat sumber data atau pada komponen yang dirender bersyarat [4].

Satu Daun Klien di Belakang Cangkang Server

Seluruh kegiatan modul ini hidup di dua komponen klien: editor 471 baris dan tampilan daftar 361 baris. Menu Kelembagaan sendiri masuk ke sidebar dengan permission organisasi:read. Kedua komponen dibuka dengan "use client", direktif yang mendeklarasikan batas antara grafik modul server dan klien [1]. Route tetap server component, hanya bertugas menulis judul, memvalidasi param, lalu menyerahkan satu anak klien.

Editor membaca izin dari sesi dan mematikan tombol tulis bagi pengguna tanpa organisasi:write. Gerbang semacam ini afordansi antarmuka, bukan batas keamanan: ia menyembunyikan tombol, sedangkan jaminan sesungguhnya tetap pada API yang menerima request. Nilainya jelas untuk pengguna, asalkan tidak dianggap sebagai lapisan pertahanan [4]. Modul ini memasang keduanya: gerbang di klien untuk kenyamanan, dan route server untuk hal yang tidak boleh bergantung pada JavaScript.

Daftar yang Menunggu, Bukan Tidur

Tampilan daftar memuat 20 baris per halaman dan mencari dengan debounce 350 milidetik lewat setTimeout yang dibersihkan dengan clearTimeout. React membersihkan Effect render sebelumnya sebelum Effect berikutnya berjalan, jadi saat ketikan beruntun pun hanya satu timeout yang terjadwal pada satu waktu [3]. Tanpa pembersihan itu, lima ketikan menghasilkan lima request.

Di dalam effect yang sama ada penjaga balasan usang: respons dari filter lama yang tiba belakangan harus diabaikan, bukan ditulis ke state. Dokumen React menyebut dua pilihan untuk kasus ini, membatalkan fetch atau mengabaikan hasilnya [3], dan daftar ini memilih yang kedua lewat bendera ignore. Setelah sebuah organisasi dihapus, daftar tidak memicu fetch baru sendiri; reload dijalankan lewat kunci refresh terpisah.

useEffect(() => {
  let ignore = false;
  const t = setTimeout(async () => {
    const res = await adminApi.listOrganizations({ q: debouncedQ, page });
    if (!ignore) { setItems(res.items); setTotal(res.total); }
  }, 350);
  return () => { ignore = true; clearTimeout(t); };
}, [debouncedQ, page]);

Kategori yang Tidak Dikirim

Detail organisasi tidak membawa field kategori, padahal form sunting harus menampilkan pilihan kategori yang benar. Editor menyelesaikannya dengan membaca tipe_cabor: BADAN_FUNGSIONAL menjadi Badan Fungsional, PENCABUTAN menjadi Pengcab Habis Masa Bakti, dan sisanya jatuh ke Cabor KONI. Data yang hilang dipulihkan dari data yang ada, bukan ditebak dari state pengguna.

Di lapisan API, kelima method admin organisasi, daftar, ambil, buat, perbarui, hapus, ditulis terhadap tipe yang dihasilkan dari skema OpenAPI. openapi-typescript mengubah skema OpenAPI 3.0 dan 3.1 menjadi TypeScript [5], sehingga daftar anggota yang ikut dalam request sunting membaca tipe yang sama dengan skema OrganizationUpsertRequest. Perubahan kontrak di sisi server langsung terbaca sebagai error di editor.

Keputusan yang paling terasa setelah commit ini: route modul dibiarkan tipis supaya satu-satunya bagian yang butuh penjagaan ketat, validasi id, berada di tempat yang paling mudah dipercaya. Sisanya, 879 baris, berada di komponen klien dan wrapper API, dua bagian yang boleh dimuat ulang sesering yang dibutuhkan.

Sumber:

Artikel terkait