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.