Have you ever thought to write an event listener like the following?
const button = document.querySelector('button');
button.addEventListener(
'click',
() => {
console.log('click');
}
);
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'));
}
}
);
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();
}
);
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'));
}
}
);
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');
}
);
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)
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.
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!