Scroll lock yang tidak pernah dilepas
Halaman berhenti bisa di-scroll di desktop, tanpa elemen yang terlihat sebagai penyebab. Pemicunya adalah memperlebar jendela browser.
- React
- useEffect
- matchMedia
- Bug
Menu mobile mengunci scroll latar dengan cara paling umum: set document.body.style.overflow ke hidden saat terbuka, pulihkan nilai sebelumnya di cleanup effect. Benar, dan di perangkat mobile memang tidak pernah bermasalah.
Urutan yang merusaknya: buka menu di viewport sempit, lalu lebarkan jendela sampai melewati breakpoint md. Menunya hilang dari layar karena class md:hidden, tetapi state open tetap true. Komponen tidak unmount, dependensi effect tidak berubah, jadi cleanup tidak pernah dipanggil — dan body.overflow bertahan hidden. Hasilnya halaman desktop yang sepenuhnya tidak bisa di-scroll, tanpa overlay, tanpa modal, tanpa apa pun yang terlihat untuk ditutup.
Ini kelas bug yang laporannya selalu terdengar mustahil: 'kadang halamannya macet'. Tidak bisa direproduksi dengan mengulang klik, hanya dengan mengubah ukuran jendela — sesuatu yang jarang ditulis orang di langkah reproduksi.
Perbaikan: sinkronkan state dengan breakpoint
CSS menyembunyikan menu, tapi CSS tidak bisa memperbarui state React. Jadi breakpoint-nya perlu diamati dari JS juga, dan effect ini juga jalan sekali saat mount untuk menangkap kasus viewport yang sudah lebar sejak awal:
useEffect(() => {
const query = window.matchMedia("(min-width: 768px)");
const onChange = () => {
if (query.matches) setOpen(false);
};
onChange();
query.addEventListener("change", onChange);
return () => query.removeEventListener("change", onChange);
}, []);Masalah kedua yang sama sumbernya
Ketika command palette ditambahkan, ia juga butuh mengunci scroll. Dua komponen yang menulis properti yang sama akan saling merusak: palette dibuka di atas menu yang terbuka, palette ditutup, cleanup-nya memulihkan overflow ke nilai yang ia catat — dan menu yang masih terbuka kehilangan lock-nya. Palette karena itu mengunci lewat elemen html, bukan body, sehingga kedua lock berdiri di properti yang berbeda dan tidak pernah bertabrakan.