Satu Nilai, Dua Makna: Scope Global Itu Pilihan, Bukan Null
Null di filter scope manggut dua tugas: belum memilih dan pilih semua. Catatan desain kenapa sentinel ALL di enum lebih jujur soal maksud.
Ringkasan
Di KotaPortal, null dipake buat dua hal sekaligus yaitu belum milih dan pilih semua unit, yang bikin logika backend jadi rancu. Akhirnya dibikin sentinel ALL sebagai member enum biar pilihannya eksplisit dan null balik jadi tanda belum milih aja. Hasilnya kontrak API lebih jelas, validasi aman, dan status tiket nggak gampang ketuker lagi.
Satu Nilai, Dua Panggilan
Pas lagi nge-wiring dropdown filter scope di aplikasi KotaPortal, saya nemu satu logika yang bikin saya berhenti sejenak. Nilai null di variabel selectedBidang ternyata dipakai buat dua hal berbeda: menandakan pengguna belum milih apa-apa, sekaligus nandain pengguna udah milih "semua unit".
Ini pola klasik: satu nilai nggak resmi ketambahan tugas tanpa kita sadari. Awalnya saya kira ini jalan pintas yang rapi. Toh, kalo nggak ada pilihan spesifik, artinya ambil semua, kan? Tapi semakin saya tarik benang logikanya ke backend, semakin jelas ini masalah. Saya sedang memaksa sebuah nilai yang seharusnya kosong untuk membawa beban makna yang spesifik.
Definisi di MDN aja udah ngomong: null itu intentional absence of any object value, sedangkan undefined absence of a value [5]. Sifat null yang falsy dan nullish bikin dia mengalir mulus lewat nullish coalescing atau optional chaining [5]. Justru karena sifat alaminya ini, memaksanya jadi penanda "semua unit" adalah resep kebingungan. Ketiadaan objek tiba-tiba punya arti "ambil semuanya".
Jebakan Nullable di OpenAPI
Masalah makin runyam pas kita bicara kontrak API. Di spesifikasi OpenAPI, kalo kita bersikeras pakai null di dalam enum, kita wajib nulisin null secara eksplisit di dalam daftar nilai enum-nya. Cuma ngasih properti nullable: true doang nggak cukup [2].
Detail ini gampang banget kelewat karena mata kita udah puas lihat nullable: true. Belum lagi tiap tooling bisa memperlakukan kombinasi nullable-plus-enum dengan cara yang beda-beda; yang aman jelas satu: kalo null emang nilai yang sah, tulis eksplisit di daftar enum.
Untungnya, TypeScript string literal unions memberi kita perilaku mirip enum dengan keamanan compile-time [6]. Kita bisa mendefinisikan tipe yang ketat tanpa menambah runtime overhead. Apalagi dibantu generator kayak openapi-typescript yang mengubah schema OpenAPI jadi tipe TypeScript bebas runtime [4]. Kita dapat kontrak yang solid tanpa dependensi berat di bundle aplikasi.
Sentinel ALL: Pilihan, Bukan Ketiadaan
Prinsip saya di sini tegas: kalo sebuah placeholder mulai mbawa dua makna, pisahkan segera. Ketiadaan harus tetap berarti ketiadaan.
Saya mengubah desainnya. Scope global sekarang diperlakukan sebagai nilai REAL yang dipilih pengguna, bukan sebagai nilai yang hilang. Saya membuat sentinel ALL sebagai member uppercase di dalam enum BidangScope — sejalan dengan keputusan bikin semua nilai eksplisit di spec yang saya tulis di kontrak API kemarin. UI dropdown pun sekarang merender ini sebagai pilihan eksplisit, bukan sekadar opsi kosong yang mengandalkan fallback.
type BidangScope = 'finance' | 'hr' | 'asset' | 'ALL';
interface FilterParams {
bidang: BidangScope;
}
Dengan pendekatan ini, null kembali ke tugas aslinya: menandakan bahwa pengguna belum nyentuh dropdown-nya sama sekali. Begitu pengguna memilih "Semua Unit", string ALL yang dikirim ke backend, bukan null. Ini menghilangkan ambiguitas di sisi server: backend nggak perlu lagi menebak apakah null berarti belum diisi atau justru ambil semua data.
Efek Domino ke Status dan Riwayat
Keputusan desain ini ternyata nggak berhenti di dropdown filter doang. Enam status tiket dan modul riwayat tiket (ticket history) di KotaPortal sekarang ikut membaca dari kontrak enum yang sama dalam satu pass.
Nggak ada lagi tebak-tebakan string mentah yang lolos ke database. Semua status dan riwayat terikat pada tipe yang sama. Untuk memastikan konteksnya tersampaikan dengan baik, deskripsi di OpenAPI Document (OAD) juga saya lengkapi dengan detail tujuan dan efek tiap nilai, melebihi yang bisa disimpulkan mesin sendiri [3].
Ketika kita memisahkan makna "belum ada pilihan" dan "pilih semua", kita sebenernya lagi ngargai kejelasan kontrak data. Aplikasi jadi lebih gampang di-debug, validasi schema lebih bisa diandalkan, dan rekan satu tim nggak perlu lagi nerka maksud dari sebuah nilai kosong. Ujinya simpel: buka satu dropdown di aplikasi kamu dan tanya, nilai apa yang keluar sebelum pengguna nyentuh apa-apa. Kalo jawabannya sama dengan nilai yang berarti sesuatu, di situ ada dua makna yang numpang di satu kursi.
## Sources [2] Swagger, OpenAPI 3.0 guide: Enums [3] OpenAPI Learn: Providing Documentation and Examples [4] openapi-typescript, README (repo openapi-ts/openapi-typescript) [5] MDN Web Docs: null [6] TypeScript Handbook: Literal Types