Search code examples
javascriptreactjskeypress

How to detect keydown anywhere on page in a React app?


I am trying to detect a key press anywhere on the page in an app built with React.

I remember it being quite easy in jQuery, but I wanted to see if it could be done in React. I didn't want to make a 'frankenstein' project i.e. have some parts in react and some parts in jQuery, if at all avoidable.

So far I have:

export default function App() {
    const handleKeyPress = (event) => {
        console.log(event.keyCode);
    }

    return (
        <div
            onKeyDown={handleKeyPress}
            tabIndex="0"
        >
            <Header page_num={100}/>
        </div>
    );
}

But it only seems to work when a user clicks on the actual div in the page, then presses a key.

I want the key press to be detected without the user having to click anywhere on the page.


Solution

  • You can use useEffect hook to achieve this and adding an event listener to document:

    import React, { useEffect } from "react";
    
    export default ({ name }) => {
      useEffect(() => {
        function handleKeyDown(e) {
          console.log(e.keyCode);
        }
    
        document.addEventListener('keydown', handleKeyDown);
    
        // Don't forget to clean up
        return function cleanup() {
          document.removeEventListener('keydown', handleKeyDown);
        }
      }, []);
    
      return <div>Keydown</div>;
    };
    

    Here is an example in action.

    Assuming the <div> is focused via tabindex or similar, you'd be able to see see in the keydown handler that e.target would be the <div>. You can also wrap the functionality in another component and in it's keydown handler check if the keydown was executed on that element instead of outside using contains.

    Here is another example that uses contains to check if the event target is within the div ref:

    import React, { useEffect, useRef } from "react";
    
    function Hello({ onKeyDown }) {
      const ref = useRef();
    
      useEffect(() => {
        function handleKeyDown(e) {
          // check if keydown was contained in target div
          if (!ref.current || ref.current.contains(event.target)) {
            return;
          }
    
          // Emit event to parent component
          onKeyDown(e);
        }
    
        document.addEventListener("keydown", handleKeyDown);
    
        return function cleanup() {
          document.removeEventListener("keydown", handleKeyDown);
        };
      }, []);
    
      return (
        <div ref={ref} tabIndex="0">
          foo bar baz
        </div>
      );
    }
    
    export default Hello;
    

    Hopefully that helps!