DEV Community

Cover image for Enter, it screams... I mean, it streams
Westbrook Johnson
Westbrook Johnson

Posted on • Edited on

Enter, it screams... I mean, it streams

Have you ever thought to write an event listener like the following?

const button = document.querySelector('button');

button.addEventListener(
    'click',
    () => {
        console.log('click');
    }
);
Enter fullscreen mode Exit fullscreen mode

And then, gotten reports that the actual work that you listed (please don't ship console.log() statements to production) was happening way too often?

Maybe you wrote your listener like this, and got similar reports?

const button = document.querySelector('button');

button.addEventListener(
    'keydown',
    ({ code }) => {
        if (code === 'Enter') {
            console.log('Enter'));
        }
    }
);
Enter fullscreen mode Exit fullscreen mode

If you've already caught the context, don't ruin the surprise...

Keyboard-based interactions

To mimic pointer interactions when navigating an application with the keyboard, when the Space key is pressed and a <button> element is in focus, the keyup event from that interaction is duplicated as a click event. Similarly, the keydown event (or keypress event, if you're into deprecated APIs) on the Enter key, in the same situation, will be duplicated as a click event.

However, the Enter key is special. The Enter key screams...I mean, streams its keydown event. That means as long as a visitor has their Enter key down the keydown event, which usually is tightly coupled to when the key goes down, will continue to fire. This means that our keydown listener AND the click listener in the above code sample will stream as well.

My own haunted house

I was recently surprised by this reality when a listener like this that I have bound threw focus into a different element that also had a listener like this bound, which subsequently through focus back into the first element and created a loop 😱

const button1 = document.querySelector('button.one');
const button2 = document.querySelector('button.two');

button1.addEventListener(
    'click',
    () => {
        button2.focus();
    }
);

button2.addEventListener(
    'click',
    () => {
        button1.focus();
    }
);
Enter fullscreen mode Exit fullscreen mode

Normally, the event listeners worked exactly as planned, but then came the bumps in the night... luckily, I have great coworkers that helped catch this use case at code review time, before it went live to users. 😅

Enter at your own risk

Maybe yelling the event listener at your visitor is the right thing to do in the context of your application, but, if it is not, key this reality in mind to ensure that your application delivers the expected experience.

If you're interested in coalescing the interaction with the Enter key to a one-time event, you might be interested in the keyup event:

const button = document.querySelector('button');

button.addEventListener(
    'keyup',
    ({ code }) => {
        if (code === 'Enter') {
            console.log('Enter'));
        }
    }
);
Enter fullscreen mode Exit fullscreen mode

If you're actually working with a click event, you may want to do a more complex form of gating:

let enterKeydown = false;

const button = document.querySelector('button');

button.addEventListener(
    'keydown',
    (event) => {
        if (event.code === 'Enter') {
            if (enterKeydown) {
                event.preventDefault();
            }
            enterKeydown = true;
        }
    }
);

button.addEventListener(
    'keyup',
    (event) => {
        if (event.code === 'Enter') {
            enterKeydown = false;
        }
    }
);

button.addEventListener(
    'click',
    () => {
        console.log('click');
    }
);
Enter fullscreen mode Exit fullscreen mode

Or, maybe you have an alternative way to not scare your visitors? If so, share it in the comments below! I'd love to hear what you've been doing to prevent the Enter key from screaming at anyone who has to listen to your application.

Top comments (2)

Collapse
 
stefanonepa profile image
stefanonepa

Nice article, thank you for sharing!

I noticed a problem in the final snippet. It seems that the binding with event is missing when you preventDefault.

I think that you should remove the destructuring in the keyDown event handler.

button.addEventListener(
    'keydown',
    (event) => {
        if (event.code === 'Enter') {
            if (enterKeydown) {
                event.preventDefault();
            }
            enterKeydown = true;
        }
    }
);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
westbrook profile image
Westbrook Johnson

Thanks for catching that. I corrected that in the actual code, but it didn't make it back to here 🙈.

Hope you find the pattern useful!