How to detect Esc Key Press in React and how to handle it

JavascriptReactjs

Javascript Problem Overview


How do I detect Esc keypress on reactjs? The similar thing to jquery

$(document).keyup(function(e) {
     if (e.keyCode == 27) { // escape key maps to keycode `27`
        // <DO YOUR WORK HERE>
    }
});

Once detected I want to pass the info down components. I have 3 components out of which last active component needs to react to the escape key press.

I was thinking of a kind of registering when a component becomes active

class Layout extends React.Component {
  onActive(escFunction){
    this.escFunction = escFunction;
  }
  onEscPress(){
   if(_.isFunction(this.escFunction)){
      this.escFunction()
   }
  }
  render(){
    return (
      <div class="root">
        <ActionPanel onActive={this.onActive.bind(this)}/>
        <DataPanel onActive={this.onActive.bind(this)}/>
        <ResultPanel onActive={this.onActive.bind(this)}/>
      </div>
    )
  }
}

and on all the components

class ActionPanel extends React.Component {
  escFunction(){
   //Do whatever when esc is pressed
  }
  onActive(){
    this.props.onActive(this.escFunction.bind(this));
  }
  render(){
    return (   
      <input onKeyDown={this.onActive.bind(this)}/>
    )
  }
}

I believe this will work but I think it will be more like a callback. Is there any better way to handle this?

Javascript Solutions


Solution 1 - Javascript

If you're looking for a document-level key event handling, then binding it during componentDidMount is the best way (as shown by Brad Colthurst's codepen example):

class ActionPanel extends React.Component {
  constructor(props){
    super(props);
    this.escFunction = this.escFunction.bind(this);
  }
  escFunction(event){
    if (event.key === "Escape") {
      //Do whatever when esc is pressed
    }
  }
  componentDidMount(){
    document.addEventListener("keydown", this.escFunction, false);
  }
  componentWillUnmount(){
    document.removeEventListener("keydown", this.escFunction, false);
  }
  render(){
    return (   
      <input/>
    )
  }
}

Note that you should make sure to remove the key event listener on unmount to prevent potential errors and memory leaks.

EDIT: If you are using hooks, you can use this useEffect structure to produce a similar effect:

const ActionPanel = (props) => {
  const escFunction = useCallback((event) => {
    if (event.key === "Escape") {
      //Do whatever when esc is pressed
    }
  }, []);

  useEffect(() => {
    document.addEventListener("keydown", escFunction, false);

    return () => {
      document.removeEventListener("keydown", escFunction, false);
    };
  }, []);

  return (   
    <input />
  )
};

EDIT for React 17: React changed the way that document-level event binding is handled, which may cause this implementation to stop working if at some point in the chain event.stopPropogation() is called. You can ensure that this function is called first by changing the last argument of the listener to true rather than false. If you do this and also call event.stopPropogation(), other handlers that used to be called will no longer take place, so I would suggest avoiding that call if at all possible.

Solution 2 - Javascript

You'll want to listen for escape's keyCode (27) from the React SyntheticKeyBoardEvent onKeyDown:

const EscapeListen = React.createClass({
  handleKeyDown: function(e) {
    if (e.keyCode === 27) {
      console.log('You pressed the escape key!')
    }
  },

  render: function() {
    return (
      <input type='text'
             onKeyDown={this.handleKeyDown} />
    )
  }
})

Brad Colthurst's CodePen posted in the question's comments is helpful for finding key codes for other keys.

Solution 3 - Javascript

Another way to accomplish this in a functional component, is to use useEffect, like this:

import React, { useEffect } from 'react';

const App = () => {
  

  useEffect(() => {
    const handleEsc = (event) => {
       if (event.keyCode === 27) {
        console.log('Close')
      }
    };
    window.addEventListener('keydown', handleEsc);

    return () => {
      window.removeEventListener('keydown', handleEsc);
    };
  }, []);

  return(<p>Press ESC to console log "Close"</p>);
}

Instead of console.log, you can use useState to trigger something.

Solution 4 - Javascript

For a reusable React hook solution

import React, { useEffect } from 'react';

const useEscape = (onEscape) => {
    useEffect(() => {
        const handleEsc = (event) => {
            if (event.keyCode === 27) 
                onEscape();
        };
        window.addEventListener('keydown', handleEsc);

        return () => {
            window.removeEventListener('keydown', handleEsc);
        };
    }, []);
}

export default useEscape

Usage:

const [isOpen, setIsOpen] = useState(false);
useEscape(() => setIsOpen(false))

Solution 5 - Javascript

React uses SyntheticKeyboardEvent to wrap native browser event and this Synthetic event provides named key attribute,
which you can use like this:

handleOnKeyDown = (e) => {
  if (['Enter', 'ArrowRight', 'Tab'].includes(e.key)) {
    // select item
    e.preventDefault();
  } else if (e.key === 'ArrowUp') {
    // go to top item
    e.preventDefault();
  } else if (e.key === 'ArrowDown') {
    // go to bottom item
    e.preventDefault();
  } else if (e.key === 'Escape') {
    // escape
    e.preventDefault();
  }
};

Solution 6 - Javascript

function handleEsc(event) {
    if (event.keyCode === 27) {
      close();
    }
  }

  useEffect(() => {
    window.addEventListener("keydown", handleEsc);
    return () => {
      window.removeEventListener("keydown", handleEsc);
    };
  }, []);

Solution 7 - Javascript

If you want to make this work directly in your component instead of a hook, or if like me you are using this in a hook that DOES NOT return a

useEffect(()=>{
    document.addEventListener('keydown', (e) => {
      e.key === 'Escape' && setOpenState(false)
    })
    return () => {
      document.removeEventListener('keydown', (e) => e)
    }
  },[openState])

Solution 8 - Javascript

React Hook

useEffect(()=>{
        document.addEventListener("keydown", keydownFunction, false);
        return  () => { document.removeEventListener("keydown", keydownFunction, false); };
    }, []);
const keydownFunction =(event)=>{
        if (event.key === "Escape") {  setAdd(false); }
        if (event.key === "+") { setAdd(true); }) }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNeoView Question on Stackoverflow
Solution 1 - JavascriptChase SandmannView Answer on Stackoverflow
Solution 2 - JavascriptpirtView Answer on Stackoverflow
Solution 3 - JavascriptJonas SandstedtView Answer on Stackoverflow
Solution 4 - JavascriptBjorn ReppenView Answer on Stackoverflow
Solution 5 - JavascriptBitOfUniverseView Answer on Stackoverflow
Solution 6 - JavascriptAhmed ElsayedView Answer on Stackoverflow
Solution 7 - JavascriptskosariView Answer on Stackoverflow
Solution 8 - JavascriptKamol BoymatovView Answer on Stackoverflow