Transcript from the "Focus Event Handling in the Dropdown" Lesson
>> Marcy Sutton: So, our list is starting to look pretty good. I've got some relationships between the button and the drop down. The mechanics of what I want to happen are, when I click on this button, I want to change the state so that it will toggle, it being open and closed.
[00:00:16] Because I could click the same button and it should just open and close. When I open it, I want to send focus to the first item in the drop down. Maybe there, I mean, theoretically, I've seen especially in angular applications where maybe the button and the list are in completely different parts of the DOM.
[00:00:33] And that's why it's important to send focus to that drop down when it opens because you need to get the user into the right spot. So, the click handler, I am going to call this set IsOpen, I get my casing right. Set IsOpen and just going to toggle IsOpen.
[00:01:22] Then I can add a string and I can also add some dynamic stuff in here. So if I add dollar sign, curly braces, I've kind of escaped out of the string and I can put logic in here. This is where this whole mechanics of react in adding these attributes, that's why I like it so much.
[00:01:39] So I've got drop down list. I want to say if it's open, I'm gonna add, if the state variable is open, I'm gonna add a class called active. And if it's not, this is a ternary operator here so I can in line have like a NFL conditional. If it's not being toggled to be open, I'm gonna pass it an empty string, because I don't want it to add a CSS class.
[00:02:07] So if I save that and I go back to the browser, I can click it, so I've got this most basic behavior and this is where most people stop. Sadly, like getting a button element to toggle would be a huge win. I can at least do that, but I'm not sending focus into the items.
[00:02:27] This is pretty good if the list is right next to the button. So in that case like that's pretty good, but I think I can do a little better by forwarding you to this first item. And then the Escape key, I wanna bind to close it. It's also debatable whether you wanna use a focus trap here to trap focus.
[00:02:49] That's what GitHub did. We're not gonna go that far but that's a decision that you could make, whether you want to make the user make a choice before they can move on. It's sort of subjective. So let's go back here. And I'm gonna add an escape key listener.
[00:03:04] So let's do onKeyUp. Take your advice, Amy, and we will add another function here. So let's do,
>> Marcy Sutton: I'm going to call it key handler, because the the handler function doesn't. It's just a function. It'll be called no matter what he you hit. And so I'm going to go add that right here with const keyHandler.
[00:03:31] I'm gonna add it in the event argument here, so that I can get a handle on what key did you actually press. And something I do a lot here is I'll do console log event.key code. I think there's event.key, there's different things that you can do. And when we get into the automated testing section, this will become more important.
[00:03:53] But I'm just going to do event.key code, so that way when I work on this and I open the developer tools, and I get over to, let's go back. The skip link to get to the main content is pretty useful. So if I hit the Escape key, that's 27.
[00:04:13] If I hit Enter, that's 13, but the escape one is the one that we really want. So I'm gonna go back over here and say, if event.key code is 27 with the triple equals, then I can start and do, I can do things to handle the Escape key.
[00:04:35] Let's say we could, I'm gonna add one more thing here. If it is open, then I wanna close it. That's that's when the Escape key's relevant. I don't want Escape to be the toggle, just because that's not really a convention. So I could do setIsOpen to false, just tell it, all right, you're done.
>> Speaker 2: Key code is actually deprecated so you should use key.
>> Marcy Sutton: Key, okay.
>> Speaker 2: Apparently.
>> Marcy Sutton: Thank you. So that's where I've just been doing the thing for so long. So let's do event.key and let's go log that and see. I think I would have had to change that eventually anyway because that doesn't work for automated testing.
[00:05:16] But old habits die hard. [LAUGH] So let's go. The nice thing I've got Gatsby Develop running, so it hot reloaded the page and now I should be able to just hit escape. So key is the string name of the key, not the key code. I think there's another, I think it's event.code maybe for the key code.
[00:05:37] So in here, we can just say, event.key equals escape. Event.code might be a little bit more reliable. Just keep that, I think that should be the same. So, now if I hit escape it has matched my keyword interaction with the code to handle it and I can tell it to do things like change state.
[00:06:02] So, now we've got the escape key part working. The last thing to, before we break will be to add that focus into the first item. So I've got a ref on the list. And this is again a bit of a subjective thing, so one thing I could do here would be to add a tab index of negative one.
[00:06:23] That would make this list itself focusible. But I found through user testing and research, that sending focus to rapper elements is actually not the best. It's better to send focus to the first interactive thing, because then you could hit Enter, you'd be there in the right spot. If a user is zoomed way in and their focus outline is cut off, it's kind of confusing.
[00:06:45] So for that reason, I'm actually not going to send focus to that list item. But where this gets a little tricky is that our first list item is dynamic, and so there's probably multiple ways that you could work around this. For my prototype here, I'm just going to go find that, so I know the general area.
[00:07:06] I know that this drop down list is like the rapper element, and I am admittedly going to go hack at this because sometimes that's what you need to do. We need actually do a little bit more set up to be able to interact with this the way that we want.
[00:07:22] So I'm gonna use another react thing here called useEffect. This is another react hook that I can pass a callback function. I'm going to make it toggle on this isOpen. So you can pass it any state variable that you're watching. You can also pass it an empty string or sorry, an empty array and it will fire once when the component loads.
[00:07:47] So in this stateless functional component, that's really useful, but I'm actually going to watch it based on whether it's open. And I can add a conditional here. So if it is open, and I think in the hook I want, it's just going to pass this in, I don't need to pass it again.
[00:08:04] So if it's open, then I'm gonna do stuff. If it's not open, I'm gonna do other stuff. And something I haven't really talked about yet is another convention, is when you open a drop down list, you kind of expect to click outside of it and close. So we can do that in this useEffect hook as well.
[00:08:23] So in this isOpen, I'm gonna handle the focus first. So I've got drop down list ref. So when you have a ref, the EPI is to do the ref that you've defined that current. And that will get you the actual element that you can focus on. So if I were focusing on the list itself, I could say drop down list ref.current.focus.
[00:08:44] In this case, current get's me the actual down element, little trick I found. So I'm just gonna say, query selector. And I don't care if this is dirty, I'm just doing it. I can get that first link in the list. And sometimes that's what you need because the list or the link inside of there if I don't have control over that, but that's where I want to send focus, that'll do it.
[00:09:08] So let's go look at that and see how that worked. So that sent my focus into the first item. I don't have any CSS animation here that I would need a delay, but the useEffect hook I found is a really great way to work around animation that could delay that being available to focus on.
[00:10:58] And those can cause issues. So let's go define this clickoutsideHandler. I'm going to give it an event. Whether or not I need it, I think I actually do in this case. So if I'm clicking outside of the component that I know about, like I can ask it, like am I clicking, and is the event target matching anything that I know about in this component.
[00:12:01] So if I click anywhere outside of this list or the toggle button, so both of those because they're separate elements, like if I click out here somewhere, that's when I want this to match. So I'm gonna do both cases. I'm going to do activator and I'm gonna wrap my code here.
[00:12:20] ActivatorRef.current.contains event.target. If it's either one of those, I'm just going to say, all right, skip it, don't do anything. Just return and that will just like pass over these. Leave it like just drop the event. Don't do anything. But otherwise, I'm gonna say, set is open to false and close it.
[00:12:46] So my useEffect, I can do two things, handle focus when it opens or when a state thing changes. I can also do these kind of hacky events. Okay, what did I miss? I missed part of this function. So the arrow function is replacing the word function, and it's pretty awesome for events.
[00:13:06] Okay, so now if I click anywhere out here, I can close it and I can hit the Escape key. So you will notice my focus outline is suppressed, and we talked about focus visible earlier. That's something I actually have in here. So if I use the mouse, I don't see a focus outline.
[00:13:26] As soon as I use the keyboard again, it will show the focus outline. So it's reacting to the users what we call input modality. So yeah, that is our drop down component.