Exercise: Time To Focus

Exercise 10 Easy

Prerequisites for the exercise

  1. React Effects
  2. React Refs

Objective

Create a SearchInput component with an input field focusable via pressing the / key.

Description

It's quite a common thing in many apps these days to use the forward slash (/) key on the keyboard to put focus on the main input field (usually, termed as the 'search bar') on the web page.

For instance, here's a screenshot from GitHub's homepage for a repository, in this case the codeguage-code repo, containing the search bar:

Search bar on GitHub
Search bar on GitHub

Notice the text that placeholds the search bar: 'Type / to search.' If nothing else in the document has focus, pressing the / key on the keyboard results in the search bar getting focus.

In this exercise, you have to create a SearchInput component that renders an input field with such a kind of functionality.

In addition to putting focus on the field via a press of the forward slash (/) key, your SearchInput component MUST also have a button next to it, reading 'Focus', that does the same thing albeit upon its click.

Here's an illustration of the rendered output of SearchInput:

Output of this exercise
Output of this exercise

Note that if the / key is pressed, as stated before, the input field MUST obtain focus right away, but more importantly, there should be nothing written to the input when the / key is pressed in order to put focus on the field.

However, once the focus is in the input field, the / key's press should work as usual — put the / character into the input field.

Note that it's desirable that you NOT utilize state to solve this exercise, because honestly it isn't really required. However, if you want to, you can use state and solve the exercise otherwise.

Anyways, following we use the SearchInput component in a dummy app:

function App() {
   return (
      <SearchInput />
   );
}

Here's the desired program that you should get in the end:

Live Example

View Solution

New file

Inside the directory you created for this course on React, create a new folder called Exercise-10-Time-To-Focus and put the .js solution files for this exercise within it.

Solution

As always, let's start by setting up the basic structure of the component before getting down to its nitty gritty details.

SearchInput renders an <input> element along with a button which, when clicked, puts focus inside the <input>. Hmm. This seems to be pretty straightforward to implement.

Following we code this:

import { useRef } from 'react';

function SearchInput() {
   const inputRef = useRef();

   return (
      <>
         <input type="text" ref={inputRef} />
         <button onClick={() => inputRef.current.focus()}>Focus</button>
      </>
   );
}

This pretty much completes more than half of the entire component's implementation; the rest is to code the key press logic. And that's to be done up next.

For the event, we'll use keydown. For the object on which to set up the event handler, we'll use window, since we need to watch for the keydown event on the entire document.

Keep in mind that watching for keydown on the entire window (or document) comes with an edge case to solve and that is if the focus is on some other input field. In this case, we clearly don't want to change the focus to another input field while the user is typing into the current one.

We'll see how to address this edge case in a later part of this solution below using JavaScript's document.activeElement property.

As for how to track whether the / key caused the given event, we can use the keyCode property of the fired event and compare it against the value 191. What's 191?

191 is the key code of the foward slash (/) key on the keyboard.

No need to memorize key codes!

You don't need to memorize these key codes if you wish to track given key presses in your apps. Here's how to find them out:

  • First take note of the keys to track.
  • Set up a keydown event handler on the entire window, logging the keyCode event property inside the handler.
  • Press the desired keys and note the key codes logged in the console.

That simple!

Since setting up the event handler on window represents a side effect of the SearchInput component, it'll require us to use the useEffect() hook.

Moreover, because the handler setup needs to be done just once, the second dependency argument provided to useEffect() would be an empty array ([]).

Let's code all this into our SearchInput component:

import { useRef, useEffect } from 'react';

function SearchInput() {
   const inputRef = useRef();

useEffect(() => { window.addEventListener('keydown', function(e) { if (e.keyCode === 191) { inputRef.current.focus(); } }); }, []);
return ( <> <input type="text" ref={inputRef} /> <button onClick={() => inputRef.current.focus()}>Focus</button> </> ); }

When we run this code, as soon as we press the / key, the input field indeed gets the focus but undesirably the / character gets entered into the input field as well.

Neither does this seem great, nor is it allowed in the exercise's description above; so let's solve it.

Fortunately, the solution is to just prevent the default action in the keydown event's handler, using the event object's preventDefault() method. However, this would produce a problem of its own.

Preventing the default action always means that when we're inside the input and we type '/', nothing would be entered. Clearly, this is a problem.

But fortunately enough, the solution is simple yet again — lay out a conditional check before proceeding forward.

Can you think of this conditional?

Well, it's just to check if the currently active element in the document, obtained via document.activeElement, is the <body> element, which simply means that no element currently has focus in the document.

This is the perfect case — in fact, the only case — when pressing the / key should put focus in the input field.

Let's implement this in our component:

import { useRef, useEffect } from 'react';

function SearchInput() {
   const inputRef = useRef();

   useEffect(() => {
      window.addEventListener('keydown', function(e) {
         if (e.keyCode === 191 && document.activeElement === document.body) {
            e.preventDefault();
            inputRef.current.focus();
         }
      });
   }, []);

   return (
      <>
         <input type="text" ref={inputRef} />
         <button onClick={() => inputRef.current.focus()}>Focus</button>
      </>
   );
}

This completes the implementation of SearchInput.

Now, let's test it by running it in a dummy app:

function App() {
   return (
      <SearchInput />
   );
}

Live Example

And this completes this exercise.