I was working on an app the other day using React Hooks and I happened upon a pattern I wanted to write up!
The App
Let's set the scene. I have an App
with two components, Box
and Warning.
The goal is to show the warning when a user "touches" the box that says Don't touch me!
.
Our initial code looks like this. At the moment, the Warning always appears.
Detecting Motion
The first thing to do is note when someone mouses over the Box
component. If we can't detect that action then we can't do anything else. We'll use the onMouseOver
event and have it print a message to our console to make sure it's working.
If we toggle the console open and mouse over the box we should see a message.
Keyboard Accessible
But not everyone uses a mouse. A user may also focus on the element via keyboard. The Box
component is a single div
which is not a tabbable element. To make it tabbable we can set tabIndex
to 0
. tabIndex
takes three possible values, -1
which removes the element from tab order, 0
which adds it, and a positive value which allows you to explicitly set the tab order of the elements on the page.
The second thing we need to add is an onFocus
event. This looks exactly like our onMouseOver
event but it's the event that is fired when a keyboard user tabs over to an element.
State hook
Now that we can react to both a mouse or keyboard event, we'll want to somehow send that information to our Warning
component. This is where React hooks come into play!
In this example, we're going to leverage useState
. The syntax looks like this.
const [value, setValue] = useState('initial value')
In our app, we want a boolean value that will tell us whether or not to display the warning. So we create isDanger
which comes along with setIsDanger
, a setter function. We'll initialize isDanger
to false
.
As it turns out, we can pass this setter function to Box
. And when we use it, it will alter the value of isDanger
.
Note that isDanger
is initialized in the parent component, App
. So even though the child component, Box
, triggered the value change, the state is still tied to App
.
We can add a console.log(isDanger)
line in App
to confirm this behavior.
Using state
Now we can pass isDanger
to our Warning
component with full confidence that it will change value when we need it to. We'll use isDanger
to conditionally render the warning message. Otherwise, return null
.
For accessibility, we'll add the role of alert
to the div. This means that whenever it appears on screen a screen reader will announce it.
"Resetting" state
And that's our example! If we want to make it a bit better we can change the events we're listening to. In our previous version, we set isDanger
to true and the warning shows up. However, the warning stays because we never set isDanger
back to false.
To handle the mouse interaction we can use mouseEnter
and mouseLeave
. For the keyboard, we want onFocus
and onBlur
. The great part about this is it doesn't change the complexity of our hook and passing state. We just reference the setIsDanger
function a few additional times.
Hooks are fun!
This is a common pattern for using state hooks. And it isn't always easy to distill these into digestible examples! So I was very excited to be able to break down this one.