event.preventDefault() vs. return false (no jQuery)

Here are a few examples that might help people to understand and troubleshoot their problems better.

TL;DR

  • on* event handlers (e.g. onclick attribute on a button element): return false to cancel event
  • addEventListener is a different API, return values (e.g. false) are ignored: use event.preventDefault().
  • onclick="<somejs>" has its own potential confusion because <somejs> is wrapped into the body of an onclick function.
  • Use the browser devtools getEventListeners API to see what your event listener looks like to troubleshoot if your event handler is not behaving as expected.

Example

This example is specific to the click event with an <a> link...but can be generalized for most event types.

We have an anchor (link) with class js-some-link-hook that we want to open a modal and prevent any page navigation from happening.

The below examples were run in google chrome (71) on MacOS Mojave.

One major pitfall is assuming that onclick=functionName is the same behaviour as using addEventListener

Let's say we have an anchor tag (link) that we want to handle with javascript when javascript is enabled. We don't want the browser to follow the link when clicked ("prevent default" behaviour).

<a href="https://www.example.com/url/to/go/to/when/no/javascript"
   class="js-some-link-hook">click me!</a>

onclick attribute

function eventHandler (event) {
    // want to prevent link navigation
    alert('eventHandler ran');
    return false;
}

function addEventListenerToElement () {
    var link = document.querySelector('.js-some-link-hook');
    link.setAttribute('onclick', eventHandler);
}
addEventListenerToElement();

Then run in the browser devtools console:

var el = document.querySelector('a.js-some-link-hook'),
        listener = getEventListeners(el).click[0].listener;
console.log(''+listener); // to avoid truncation of output

...and you see:

function onclick(event) {
function eventHandler (event) {alert('eventHandler ran'); return false;}
}

This doesn't work at all. When using onclick= your handler function is wrapped in another function.

You can see that my function definition is included but not called because I specified the function reference without invoking it. i.e. we need onclick="functionName()" not onclick="functionName" to make sure functionName is run when the element is clicked.

Further you can see that even if my function was called and my function returned false...the onclick function would not return that false value...which is required to 'cancel' the event.

In order to fix this, we can set onclick to be return myHandlerFunc(); which ensures that onclick returns the return value (false) from myHandlerFunc.

You could also remove the return false; from myHandlerFunc and change the onclick to be myHandlerFunc(); return false; but this makes less sense as you probably want to keep the logic together in your handler function.

Note when setting onclick with javascript, when you are setting onclick directly in the html rather than with javascript (like my examples), the onclick attribute value is type string and everything works. If you are setting onclick using javascript, you need to pay attention to type. If you say element.setAttribute('onclick', myHandlerFunc()) myHandlerFunc will be run right now and the result will be stored in the attribute...rather than running on each click. Instead you should make sure the attribute value is set as a string. element.setAttribute('onclick', 'return myHandlerFunc();')

Now that we see how that is working, we can modify the code to do whatever we want. Odd example for illustration purposes (don't use this code):

function eventHandler (e) {
    // want to prevent link navigation
    alert('eventHandler ran');
    console.log(e);
    return false;
}

function addEventListenerToElement () {
    var link = document.querySelector('.js-some-link-hook');
    link.setAttribute('onclick', 'return ('+eventHandler+')(event);');
}
addEventListenerToElement();

You see we have wrapped our eventHandler function definition into string. Specifically: a self-executing function with a return statement at the front.

Again in chrome devtools console:

var el = document.querySelector('a.js-some-link-hook'),
        listener = getEventListeners(el).click[0].listener;
console.log(''+listener); // to avoid truncation of output

...shows:

function onclick(event) {
    return (function eventHandler (e) {
        // want to prevent link navigation
        alert('eventHandler ran');
        console.log(e);
        return false;
    })(event);
}

...so yeah, that should work. Sure enough if we click on the link we get the alert and dismissing the alert the page doesn't navigate anywhere or refresh.

One more note about onclick... If you want to receive and utilize the event parameter at the time of the event, you should note that it is named "event". You can access this within your handler using the name event (available via parent onclick function scope). Or you can construct your handler to take event as a parameter (better for testing)...e.g. onclick="return myEventHandler(event);" or as you see in the previous example.

addEventListener

function eventHandler (ev) {
    // want to prevent link navigation
    alert('eventHandler ran');
    console.log(ev);
    return false;
}

function addEventListenerToElement () {
    var link = document.querySelector('.js-some-link-hook');
    link.addEventListener('click', eventHandler, false);
}
addEventListenerToElement();

browser devtools:

var el = document.querySelector('a.js-some-link-hook'),
        listener = getEventListeners(el).click[0].listener;
console.log(''+listener); // to avoid truncation of output

result:

function eventHandler (ev) {
    // want to prevent link navigation
    alert('eventHandler ran');
    console.log(ev);
    return false;
}

So you can already see the difference. With addEventListener we aren't wrapped in an onclick function. Our handler receives the event parameter directly (and so we can call it whatever we want). Also our return false is at the "top level" here and we don't have to worry about adding an extra return statement like with onclick.

So it looks like it should work. Clicking on the link we get the alert. Dismiss the alert and the page navigates/refreshes. ie the event was NOT cancelled by returning false.

If we lookup the spec (see resources at bottom), we see that our callback/handler function for addEventListener does not support a return type. We can return whatever we want, but since it isn't part of the browser API/interface, it doesn't have any effect.

Solution: using event.preventDefault() instead of return false;...

function eventHandler (ev) {
    // want to prevent link navigation
    ev.preventDefault();
    alert('eventHandler ran');
}

function addEventListenerToElement () {
    var link = document.querySelector('.js-some-link-hook');
    link.addEventListener('click', eventHandler, false);
}
addEventListenerToElement();

browser devtools...

var el = document.querySelector('a.js-some-link-hook'),
        listener = getEventListeners(el).click[0].listener;
console.log(''+listener); // to avoid truncation of output

gives...

function eventHandler (ev) {
    // want to prevent link navigation
    ev.preventDefault();
    alert('eventHandler ran');
}

...as expected.

Testing again:

  • Click link.
  • Get the alert.
  • Dismiss alert.
  • No page navigation or refresh happens...which is what we want.

So with addEventListener use event.preventDefault() as returning false does nothing.

Resources

  • https://www.w3.org/TR/dom41/#dom-eventtarget-addeventlistener
    • Event Registration (e.g. addEventListener)
  • https://www.w3.org/TR/dom41/#callbackdef-eventlistener
    • callback/handler/listener def.
    • NOTE return type is void. ie return value is not part of the interface and has no effect.
  • https://www.w3.org/TR/dom41/#event
    • Event Object (e.g. Event.preventDefault)

The html5 spec (https://www.w3.org/TR/html5/webappapis.html#events) confuses things because they use both onclick and addEventListener in their examples and they say the following:

The event handler processing algorithm for an event handler H and an Event object E is as follows:

...

  1. Process return value as follows:

...

If return value is a Web IDL boolean false value, then cancel the event.

So it seems to imply that return false cancels the event for both addEventListener and onclick.

But, if you look at their linked definition of event-handler you see:

An event handler has a name, which always starts with "on" and is followed by the name of the event for which it is intended.

...

Event handlers are exposed in one of two ways.

The first way, common to all event handlers, is as an event handler IDL attribute.

The second way is as an event handler content attribute. Event handlers on HTML elements and some of the event handlers on Window objects are exposed in this way.

https://www.w3.org/TR/html5/webappapis.html#event-handler

So it seems that the return false cancelling the event really does only apply to the onclick (or generally on*) event handlers and not to event handlers registered via addEventListener which has a different API.

Since the addEventListener API is not covered under the html5 spec (only the on* event handlers)...it would be less confusing if they stuck to the on* style event handlers in their examples.


The W3C Document Object Model Events Specification in 1.3.1. Event registration interfaces states that handleEvent in the EventListener has no return value:

handleEvent This method is called whenever an event occurs of the type for which the EventListener interface was registered. [...] No Return Value

under 1.2.4. Event Cancelation the document also states that

Cancelation is accomplished by calling the Event's preventDefault method. If one or more EventListeners call preventDefault during any phase of event flow the default action will be canceled.

which should discourage you from using any effect that returning true / false could have in any browser and use event.preventDefault().

Update

The HTML5 spec actually specifies how to treat a return value different. Section 7.1.5.1 of the HTML Spec states that

If return value is a WebIDL boolean false value, then cancel the event.

for everything but the "mouseover" event.

Conclusion

I would still recommend to use event.preventDefault() in most projects since you will be compatible with the old spec and thus older browsers. Only if you only need to support cutting edge browsers, returning false to cancel is okay.