Are your event listeners not working as you’d expect? It’s a common but potentially complex problem. In this post, CookiesHQ Tech Lead Romaric guides us through the three key areas for troubleshooting to get everything working as it should.
Browsers provide a very neat system of events. This is how applications can provide custom behaviour for clicks, keypresses, or in response to a variety of situations. Sometimes though, you have a piece of code meant to respond to one of those events and… nothing happens. It can be quite tricky to get to the bottom of why your event listeners are not working or triggering, so here are a few possible sources for the issue.
How DOM events work
Events are dispatched by specific elements (their target
). Code can then add listeners to run custom logic in response. But it’s not only on the target that you can add listeners.
Events will actually travel through all their parents, giving each a chance to register a response. Some will go through them twice, once going from parent to child (during what’s called the capture
phase) and then once going the other way around (the bubbling
phase).
This makes a very powerful system where different parents can take separate actions for the same event. At each step, custom code can run to do whatever the site/app needs it to, but also to prevent the default action taken by the browser or even stop the event from continuing its journey.
It pays to have a good understanding of that system when dealing with events. The DOM Events site provides a great playground for that. It lets you visualise the journey of the events and play with different settings and actions taken by the listeners. If you prefer reading, the Mozilla Developer Network is always a good source. And for the inner details, there are the WHATWG specifications.
But enough theory, let’s see in practice how you can hunt why the code handling an event is not running.
Are you listening?
If your event listener not working is dependent on some logic, whether it’s about which element it’ll listen on or if it’s registered at all, the first step is to check that the listener is indeed added to the element.
Using a breakpoint in the developer tools , a logpoint or console.log (but make sure you clean it after 😉) before your code adds the listener, you verify that:
- Â yes, the listener is added
- Â yes, the element on which it is added may receive the event (it’s a parent of the element you expect to dispatch that event).
It may sound silly, but if your codebase is a little complex, it may well be that the branch of code you’d think would add the listener is never executed. And with no listener – well, there’s no chance for the code to run.
If you’re using Firefox, you may be able to skip using logs or breakpoints for running that check. The inspector panel in its developer tools shows an "event" button next to elements with event listeners attached. Clicking it will reveal the list of listeners registered on this element, as well as their source. Finding the exact listener you’re after in the often long list of listeners (thanks, event delegation) can be a good quick first step before adding extra logs.
Is the event fired?
A second thing to look for is that the event is actually dispatched. Custom events you’d expect to fire from third-party libraries may actually not be running when you thought they would. But native events like clicks can also be affected, e.g. if something overlays a button, you were expecting to be activated.
If the debug/sources panel in the devtools gives you access to the code triggering the event (either it’s your own, an unminified third-party file or one with sourcemaps), you’re in luck.
You can use a similar method as you would for checking that the listener is registered. This time, you’ll want to break or log before the event is dispatched. And if the execution doesn’t break or nothing appears, that’s be a new lead: why is the event not fired in the first place?
When it’s a native event or the code is heavily minified, tough luck with placing a breakpoint. In that situation, you can monitor which events are fired, taking advantage that they’ll start their journey on the document
itself.
If you’re using Chrome, the console in the DevTools provides a handy monitorEvents
function. In other browsers, you can add a custom event listener.
The earlier its execution, the better – to ensure no other piece of code gets in its way of capturing the event. An ideal place would be inline first thing inside the <head>
tag.
But if that’s not possible, the top of your main JavaScript file or even inside the DevTools console right after page load might be early enough to get you whether the event is fired.
Here are 3 examples of such listeners:
- one for breaking the execution and looking at the debugger
- one for just logging the event
- one logging a few more information: the event type, its target, the event itself and a stacktrace to see what triggered it
Breaking will be useful for punctual events like clicks. It’ll also let you look at the call stack to see what triggered the event in case it’s fired by code. But if your event fires rapidly (like the resize
event) or you want to look at a succession of events, logging will likely be more useful.
// For breaking document.addEventListener(EVENT_TYPE, function() {debugger;}, true) // For logging document.addEventListener(EVENT_TYPE,console.log, true); // For logging multiple informations document.addEventListener(EVENT_TYPE, function(event) { console.log( event.type, // The type of the event event.target, // The target of the event event, // The event itself (() => {try {throw new Error();} catch(e) {return e;}})() // A stacktrace to figure out what triggered the event ); }, true);
Can it stop?
The event is firing, but still, nothing happens? Maybe something is getting in the way of the event’s propagation. An earlier listener may be stopping its propagation altogether. Or if you were expecting the browser’s default behaviour to still happen, preventing it.
Overriding the Event‘s methods to intercept their calls, we can see if such a thing is happening, which the following snippet offers. Timing is less critical regarding when it runs (unless you’re investigating something like DOMContentLoaded
that happens very early), but again, the earlier you run it, the better.
The function offers you 3 options to narrow down which events you’re monitoring:
methods
to pick which of the 3 cancellation functions to look into (stopPropagation
,stopImmediatePropagation
orpreventDefault
)type
to chose whether tolog
orbreak
the executionshould
to provide a Function to decide whether to consider specific events (likely based on their type or target)
/** * Helps finding out what stops events handling by logging or breaking * when any of the stopage methods (stopPropagation
,stopImmediatePropagation
,preventDefault
) * are called. * @param {Object} options * @param {Array<String>} [options.methods]Â - The list of methods to monitor * @param {'log'|'break'} [options.type]Â - Whether to log or break when the method is called * @param {Function} [options.should] - A function to test whether the call should be logged or broken, useful for limiting noise */ (function debugEventStoppages({ methods = ['stopPropagation', 'stopImmediatePropagation', 'preventDefault'], type = 'log', should = () => true } = {}) { methods.forEach(method => { const original = Event.prototype[method]; Event.prototype[method] = function() { // Test if we need do log/break for this particular event if (should(this)) { if (type == 'break') { debugger; } else { // Log which method was called, the event type, // the event itself and a stacktrace to figure out what happened console.log( method, this.type, this, (() => { try { throw new Error(); } catch (e) { return e; } })() ); } } return original.apply(this, arguments); }; }); })();
This is by no means an exhaustive list of everything that can result in event listeners not working, or not being handled as you would expect. But hopefully, these 3 methodical steps will help you either quickly converge on the source of the issue, or swiftly rule them out to hunt it in different places.
Need tech support for your app or website? Get in touch to see if we can help →