D
P
0

WordPress & JavaScript

Tombol Wallet Square Muncul Lagi Padahal Sudah `display:none`? SDK-nya Inject Belakangan, Lawan Pakai MutationObserver

14 Juli 2026·4 menit baca
Tombol Wallet Square Muncul Lagi Padahal Sudah `display:none`? SDK-nya Inject Belakangan, Lawan Pakai MutationObserver

Sebuah toko WooCommerce yang saya pegang minta satu hal yang kedengarannya sepele: sembunyikan tombol wallet Square, yang tombol Apple Pay dan Google Pay itu, dari halaman single-product. Mereka cuma mau checkout kartu biasa. Saya pikir ini pekerjaan lima menit. Tambah satu aturan CSS, display:none, selesai. Saya salah besar.

.wallet-button, .apple-pay-button {
  display: none !important;
}

Saya deploy, refresh halaman, tombolnya hilang. Puas, saya tutup tab. Lalu tester klien buka halaman produk lain, dan tombol wallet itu muncul lagi dengan santai di bawah harga. Saya buka halaman yang sama, hard-refresh, kadang hilang, kadang berkedip muncul sepersekian detik lalu baru hilang, kadang nangkring di situ dan tidak mau pergi. Perilaku yang tidak konsisten itu justru yang paling bikin frustrasi, karena artinya bukan salah selektor saya.

Gejalanya

Yang saya lihat: aturan display:none !important itu ada, saya cek di DevTools, selektornya cocok, tapi tombolnya tetap tampil. Kadang. Di reload yang sama, kadang tersembunyi, kadang tidak. Buka Elements panel saat tombol tampil, dan node-nya ada di DOM dengan style inline sendiri yang menimpa CSS saya. Saya tidak menulis inline style itu. Berarti ada yang lain yang menaruhnya di sana setelah halaman selesai load.

Refleks pertama saya jelek, seperti biasa: saya kira spesifisitas CSS-nya kalah, jadi saya tambah !important dan naikkan selektornya. Tidak ngefek. Saya kira ada cache, saya purge semuanya. Tidak ngefek. Baru setelah saya benar-benar memperhatikan bahwa tombolnya berkedip masuk beberapa saat setelah sisanya render, saya sadar saya sedang melawan sesuatu yang tidak ada waktu saya menulis aturannya.

Kenapa ini terjadi

SDK pembayaran Square tidak merender tombol wallet-nya di HTML awal. Dia inject tombol itu ke DOM secara asynchronous, setelah halaman load, setelah SDK-nya sendiri selesai fetch dan inisialisasi. Jadi urutan waktunya begini: browser mem-parsing HTML, menerapkan CSS saya ke elemen yang ada saat itu, halaman terlihat siap. Aturan display:none saya berjalan sempurna, tapi terhadap DOM yang belum berisi tombol wallet. Beberapa ratus milidetik kemudian, SDK Square baru menyisipkan tombolnya, kadang lengkap dengan inline style-nya sendiri.

CSS itu bukan program yang menunggu. Dia diterapkan sekali terhadap apa yang ada, dan memang idealnya browser menerapkan aturan ke node baru juga, tapi begitu Square menaruh inline style di elemennya sendiri, aturan stylesheet saya kalah oleh inline style itu. Ditambah lagi Square kadang me-render ulang tombolnya secara async, misalnya setelah metode pembayaran berubah, dan setiap render ulang menaruh node baru yang bersih dari jejak saya. Jadi bukan satu momen yang perlu saya tangani, tapi aliran momen yang tidak terduga.

Begitu logikanya klik, kekonsistenan tadi masuk akal. Kalau reload-nya cepat dan SDK Square telat, aturan saya "menang" karena tombolnya belum ada saat saya lihat. Kalau SDK-nya keburu inject sebelum saya sempat lihat, tombolnya nangkring. Tidak ada yang rusak. Yang rusak cuma asumsi saya bahwa elemen yang ingin saya sembunyikan sudah ada di DOM saat kode saya jalan.

Perbaikannya

Solusinya bukan CSS yang lebih agresif. Percuma menyembunyikan sesuatu yang belum lahir. Solusinya adalah menonton container-nya dan menghapus tombol itu begitu Square menyuntikkannya, lalu terus menonton supaya render ulang berikutnya juga kena. Untuk itu MutationObserver pas sekali: dia memberi tahu saya persis ketika DOM berubah, jadi saya bisa bereaksi terhadap node yang muncul belakangan.

document.addEventListener('DOMContentLoaded', function () {
  var container = document.querySelector('.payment-container');
  if (!container) return;
 
  var observer = new MutationObserver(function () {
    var wallet = container.querySelector('.wallet-button, .apple-pay-button');
    if (wallet) {
      wallet.remove();
    }
  });
 
  observer.observe(container, { childList: true, subtree: true });
});

Bedanya halus tapi penting: saya tidak menyembunyikan tombolnya, saya menghapus node-nya dari DOM. Menghapus artinya tidak ada inline style yang bisa dilawan, tidak ada elemen yang bisa berkedip masuk. Dan karena observer-nya tetap hidup, saat Square me-render ulang dan menyuntik tombol baru, callback-nya jalan lagi dan langsung menghapusnya sebelum sempat tampil. Itu yang menghentikan flash sekaligus kemunculan yang tidak konsisten tadi.

Satu hal terakhir yang penting: skrip ini hanya perlu jalan di halaman single-product, tempat tombol wallet itu ada. Menjalankan observer di setiap halaman itu boros dan bisa memicu efek samping tak terduga di tempat lain. Jadi saya enqueue skripnya dengan guard di sisi PHP, hanya kalau is_product():

add_action( 'wp_enqueue_scripts', function () {
  if ( ! is_product() ) {
    return;
  }
  wp_enqueue_script(
    'hide-square-wallet',
    get_stylesheet_directory_uri() . '/js/hide-square-wallet.js',
    array(),
    '1.0.0',
    true
  );
} );

Pelajaran

Kalau sebuah elemen muncul lagi padahal CSS-mu jelas menyuruhnya hilang, berhenti menajamkan selektor dan tanya dulu: apakah elemen ini sudah ada saat aturanku dijalankan? SDK pihak ketiga seperti Square sering inject UI secara async setelah load, dan CSS statis maupun query JS sekali jalan tidak bisa menangkap sesuatu yang lahir belakangan. Kalau lawanmu adalah DOM yang berubah dari waktu ke waktu, jawabannya adalah alat yang juga bergerak seiring waktu: MutationObserver yang menonton container, menghapus node saat muncul, dan tetap berjaga untuk render ulang berikutnya. Dan selalu batasi jangkauannya, di sini cukup lewat guard is_product(), supaya solusimu tidak jadi masalah baru di halaman lain.