This next mistake is a crucial one to avoid because if you don't, you're going to end up causing performance issues for your users and you're going to have an app ultimately that is going to end up crashing as it's being used over time. All right, so how does this mistake work? Well, I've got a little example set up where we have a command palette that's fairly common in today's applications. This is something like where you have the page focused, and then anywhere on the website, you could press Command-K to open up some kind of input where you could do things like search different documents on the site or perform certain commands on the site or something like that.
My little implementation is super simple. It doesn't actually do anything except for when I press Command K on my Mac, there is a little input that shows up on the page. When I press Command K again, it disappears. Okay, so in order to make this work, we have to have a global event listener on our document body so that no matter where I'm at on the page, the keyboard shortcut works, right?
So inside of this command palette component here, that powers the input that displays on the page, but also powers the keyboard shortcut. So in this component, you can see that we're displaying the input of reactive data property is true. Up inside of the script setup section, I do have that defined initially as false. Then whenever the key down event occurs on the document body, I'm calling a function called handle keyboard shortcut.
inside of the handle keyboard shortcut function, we are listening to see if the meta key that is command on Mac, control I believe on Windows if I'm not mistaken, and if the other key pressed is the K key. So in other words, I want to press command K in order to execute this code below. Then we just console log something to the console and then change my active value to the opposite of whatever it was before. preventDefault just to make sure no default behaviors happen.
I want this to be my app's own little keyboard shortcut for the command palette. OK, awesome. So as you saw a moment ago, this works pretty well, right? I hit Command-K, it shows.
I hit Command-K again, it disappears. If I open up my console, sure enough, we do get the console log there as well. Let's say that for one reason or another, the command palette component is removed from use. I've simulated that by providing a checkbox input on the page where we can disable or enable the command palette altogether.
And you can see over on the left-hand side the VF that makes that function. So now let's disable the command palette. This time, I'm going to hit Command-K. And sure enough, because the command palette is not mounted to the DOM, the input does not appear.
However, if I open up the developer tools, you might have noticed that the message number there, the little five next to it, has incremented. It was four before. Let me press Command K again so that you can see it move in real time. I'll do it just a few times.
Yeah, sure enough, even though the command palette is disabled, my event listener is still active. What's worse is that now when I enable the command palette again, the component is recreated, its mount hook fires again, and now when I press Command-K, you'll notice the number is incrementing by two. So it went from eight to ten, ten to twelve, twelve to fourteen, and so on. So now we've actually doubled up on our event listeners.
And every time I enable or disable the command palette, we're going to keep on adding more event listeners. And it's all kind of hidden behind the scenes. We wouldn't have even noticed it if I hadn't added in the console lock. So what's the solution to this?
Well, it's actually pretty simple. Back over in command palette dot view, just like we created the event listener on mounted, now we just need to remove the event listener on unmounted. So I'll import the function from view. We'll call it.
And in the callback, we'll say document dot body dot remove event listener. Listen, of course, for the key down event once more, and pass it the same handler function. Excellent. So I'll save that and back over my browser.
Let's give the page a refresh for good measure. I'll hit Command-K a few times, open up the DevTools. And yeah, sure enough, we do have the message logged. This is good because we're still enabled.
But now when I disable the command palette and hit Command-K a few more times, All right, look at there. Now we're no longer getting the console log. So we have effectively removed the event listener. And as soon as we enable the command palette again, the messages start returning, but only incrementing by one, not by two or three or more, depending on how many times it's been enabled and disabled.
Awesome. So that's really the most straightforward way to handle this issue. Now, you could do it, though, in less lines of code and with a little less intentional thought about what's actually occurring if you use a helper library like Vue Use. So I've already installed Vue Use in the project.
So I want to import from Vue Use core a composable called useEventListener. body, tell it once again the event, which was keyDown, and then pass it the handler. Awesome. Now we've reduced those, what, four or five lines down to a single line?
And this should work great as well. Over in the browser, I'll give the page a refresh, open up my console, press Command-K a few times. Yes, we get the console message. Disable my command palette, press Command-K a few more times.
And yeah, the message is no longer logged. So under the hood, what this view use composable is doing is automatically removing the event listener on unmounted for us. If you're working with an SSR application, there is one more little thing you need to do here. And that is make sure we only call use event listener on mounted.
However, if you're working in just a regular SPA, this isn't strictly necessary. Finally, if your use case for adding these event listeners to the document body does involve listening to presses on your keyboard keys, you could alternatively use the useMagicKeys composable from Vue use, and it'll do the same cleaning up on unmounted, but also gives you some really nice shortcuts for working with keyboard keys.