Thinking in React can feel weird if you're not used to it. In this post, I'm going to show you a React-way to think about and use the native browser's APIs and hook them into your React app.
Specifically, I'm going to show you how to listen for keystrokes in a React-y way. Keyboard shortcuts are making a comeback in webapps -- but this pattern is more than listening for keystrokes: it's a way of thinking: the React Way.
Here's what we're going to build:
You can find the completed Codepen here:
We're going to build a custom React hook that listens for keystrokes and lets us know when a certain key is pressed.
As you might have guessed, we have a book that teaches intermediate React patterns with TypeScript. It's called Fullstack React with TypeScript and you can get a copy here
In this post, we're going to look at:
How to create our own hook
useEffect()hook and when to use it
Where to hook into the native browser APIs in React
How to use TypeScript to type the whole thing
The end goal:
When we're done, we will have a custom hook called
usePressObserver will accept a
watchKey argument, which specifies the key we're watching. It will return a
pressed variable which will either return
false depending on if the key is pressed down.
We can use that
pressed value in another component. In this case, we'll pass it as the
active prop to the
So how do we get there?
The browser supports "event listeners".#
You can listen to any key press by listening to the
"keydown" event, like this:
And you can listen to any key release by listening to the
"keyup" event, like this:
Now, what is the second argument here?
handlePressFinish are callback functions -- that is, functions that the browser will call whenever the appropriate event happens.
So far, so good, but if we're using React where do we even put this??
If we just throw a
document.addEventListener into a component we will add a new listener on every re-render. Not what we want.
Does React provide any functionality that will allow us to only call a function only once? Yes. Well, sort of.
useEffect hook lets you perform side effects. The API to
useEffect looks like this:
effectFn is a function argument. That is, it is the function that contains the effect we want to do. We'll look at that in a moment.
arrayOfThingsToWatch is an array of variables which React will watch. If any of those changes, React will call the
effectFn again -- but if they don't change, it won't be called again, which is exactly what we want.
So pseudocode of what we're trying to do looks like this:
So let's fill in the blanks.
The first thing we want to do is define our custom hook. Here's the skeleton:
We start by defining a
Settings interface, which defines our options for
this hook. In this case, we'll just have one option
watchKey, which is the
key we want to watch.
Next, we use the
useState hook to keep track of if this key is
pressed or not.
I'm going to assume you're familiar with the
useStatehook. If not, check out our blog post which is An introduction to Hooks in React
Notice, too, that we return
pressed as the result of this hook.
pressed is the primary value we're trying to extract, so that's what we return from the hook.
So this gives us a hint to our implementation of
handlePressStart -- what needs to happen there? We need to call
setPressed to tell React that our key is pressed.
Notice something here: we have to check to make sure the key that was pressed is the key we care about. Remember way back above that we said
"keydown" will trigger for any key. So we have to filter our key code here.
handlePressFinish is similar:
Are we done? Not quite. We need to "clean up" after ourselves. Because React will re-render components, we need to make sure that if this component is re-rendered that we
removeEventListeners that we created.
We do this by returning a cleanup function from the
Putting it all together, we get this:
It can look daunting altogether, but when you break it down it's not too bad.
For the sake of space, I've had to leave a few bits of code out, but you can find the whole code example here on Codesandbox:
So there you go! That's how you think about some native browser APIs in a React-y way.
If you want to learn more about intermediate React patterns with TypeScript, check out Fullstack React with TypeScript: