D
P
0

WordPress & PHP

Buttons “Dead” Only on the Homepage: A Global Click Handler in a JS File Not Enqueued There

June 28, 2026·4 min read
Buttons “Dead” Only on the Homepage: A Global Click Handler in a JS File Not Enqueued There

The heart-shaped favorite buttons on a multi-listing site I built worked perfectly everywhere. Click on the search page, fine. Click on a listing detail page, fine. Click on a category page, fine. But on the homepage? Completely dead. No animation, no request, nothing. The button rendered, you could hover it, but clicking produced no reaction at all.

The markup was identical on every page, because the button came from a single shared partial:

<button class="favorite-btn" data-listing-id="412" aria-pressed="false">
  <svg class="icon-heart">...</svg>
</button>

And the handler used event delegation on document, so any matching element from anywhere on the page should have been covered. It looked roughly like this:

document.addEventListener('click', function (e) {
  const btn = e.target.closest('.favorite-btn');
  if (!btn) return;
 
  const listingId = btn.dataset.listingId;
  toggleFavorite(listingId, btn);
});

This code is correct. There is no logic bug inside it. Yet on the homepage it behaved as if it did not exist. My first instinct pointed the wrong way: I suspected a transparent overlay sitting on top of the button, or some stray pointer-events: none in the CSS. I burned a fair amount of time there before realizing I was debugging the wrong thing.

Why this happens

One simple move saved me: open DevTools on the homepage, go to the Console, then the Network tab. I searched for the JS file that contained the handler. The file was search.js, and on the homepage it was nowhere in the Network tab. Not a 404, not a failed load. The browser had simply never requested it.

The moment I saw that, everything fell into place. The delegated handler lived inside search.js, and search.js was only wp_enqueue_script'd on the search page, behind an is_page-style guard:

function listing_enqueue_assets() {
    // BUG: search.js loads only on the search page,
    // but the favorite handler inside it is used across the WHOLE site.
    if ( is_page( 'search' ) ) {
        wp_enqueue_script(
            'listing-search',
            get_template_directory_uri() . '/assets/js/search.js',
            array(),
            '1.0',
            true
        );
    }
}
add_action( 'wp_enqueue_scripts', 'listing_enqueue_assets' );

On the other pages that "still worked", this file happened to be pulled in through another path, or those pages were themselves descendants of the search context. But the homepage stood on its own, outside the is_page('search') guard, so search.js was never loaded there.

And that is the heart of the misunderstanding: event delegation does let a single handler cover every matching element, even ones added later. But delegation buys you nothing if the script itself never executes on that page. document.addEventListener only runs when the file that contains it is actually loaded. No script, no listener. No listener, dead button. It looked like a homepage-only bug, but it was purely an enqueue-scope bug.

The fix

The fix is not to add another guard for the homepage. That just patches one hole while leaving others open. This favorite handler is global, used across the entire site, so it does not belong in a file scoped to the search page.

I moved the global handler into a file that is enqueued on every page with no condition, for example main.js:

function listing_enqueue_assets() {
    // Global: loaded on EVERY page, unconditionally.
    wp_enqueue_script(
        'listing-main',
        get_template_directory_uri() . '/assets/js/main.js',
        array(),
        '1.0',
        true
    );
 
    // Page-specific: only where it is actually used.
    if ( is_page( 'search' ) ) {
        wp_enqueue_script(
            'listing-search',
            get_template_directory_uri() . '/assets/js/search.js',
            array(),
            '1.0',
            true
        );
    }
}
add_action( 'wp_enqueue_scripts', 'listing_enqueue_assets' );

The delegated click handler for .favorite-btn moved into main.js, while search.js kept only the genuinely search-specific things like filters and autocomplete. Then I opened the Network tab on each kind of page and confirmed main.js always showed up, then clicked the heart button on the homepage. Alive instantly.

While I was there, I did the thing I should have done from the start: I audited every wp_enqueue_script condition in the theme. I mapped each file to one simple question, "is the behavior inside this global, or page-specific?" Some scripts turned out to be scoped too narrowly, and a few were loaded everywhere despite being used on a single page. Fixing that mapping prevents the same class of bug from reappearing elsewhere.

The takeaway

A delegated event handler only works on the pages where its script is actually loaded. That is the whole story. The moment an interaction works on some pages but not others, do not start tearing apart the handler logic. First check whether the JS file is even present in the Network tab on the failing page. If the file is not there, the problem is not the handler, it is the enqueue scope.

The mistake that cost me time was assuming that identical code meant identical behavior. The handler code was the same on every page, but what brings it to life is not the code itself, it is whether the file is loaded. In WordPress, "loaded or not" is a decision made by wp_enqueue_script and all its conditions. So before you debug the logic, debug the loading. The Network tab will often answer the question faster than an hour spent rereading JavaScript that was already correct.