170

I want to use a keyDown event on a div in React. I do:

  componentWillMount() {
      document.addEventListener("keydown", this.onKeyPressed.bind(this));
  }

  componentWillUnmount() {
      document.removeEventListener("keydown", this.onKeyPressed.bind(this));
  }      
  
  onKeyPressed(e) {
    console.log(e.keyCode);
  }
    
  render() {
    let player = this.props.boards.dungeons[this.props.boards.currentBoard].player;
    return (
      <div 
        className="player"
        style={{ position: "absolute" }}
        onKeyDown={this.onKeyPressed} // not working
      >
        <div className="light-circle">
          <div className="image-wrapper">
            <img src={IMG_URL+player.img} />
          </div>
        </div>
      </div>
    )
  }

It works fine, but I would like to do it more in React style. I tried

onKeyDown={this.onKeyPressed}

on the component. But it doesn't react. It works on input elements as I recall.

Codepen

How can I do it?

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
Michael
  • 6,823
  • 11
  • 54
  • 84
  • 1
    Personally I like your approach. This looks like a good way to bind the key strokes to the document, which is outside of the scope of your component. and does not require focus on a particular element. – Daniel Nov 08 '19 at 19:25
  • Also take a look at this: https://www.pluralsight.com/guides/event-listeners-in-react-components – Shahriar Jul 20 '22 at 09:22

7 Answers7

259

You should use tabIndex attribute to be able to listen onKeyDown event on a div in React. Setting tabIndex="0" should fire your handler.

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
burak
  • 3,839
  • 1
  • 15
  • 20
  • 8
    This seems like bad, opinionated design. What if I want to handle a bubbling event on a container element but that container itself should not ever be focused? – Will P. Jan 27 '20 at 18:19
  • 1
    This seems like more of a hack or workaround to me. There may be a time when you need to have the given div maintain a tab order. – Iwnnay Feb 18 '20 at 21:54
  • 32
    `tabIndex={-1}` can also be used if you're working with an element that isn't interactive and don't want to change the tab order of the document. – IliasT Mar 18 '20 at 23:41
  • 1
    you have to set the tabIndex, to make the div focusable. You can set the tabIndex to 0,1,-1.... But before you do that check this out https://webaim.org/techniques/keyboard/tabindex You'll likely want to set to -1 since you're manipulating it programmatically. – Kavita May 06 '20 at 14:13
  • for some reason this only works if I have an element in the div with the tabIndex. Or something else that can be focussed. With just other divs, its still not capturing. Any thoughts? – Flion Jun 27 '20 at 05:14
  • adding tabIndex is not working for me, I am trying to get the value from the input and I have this code: `
    handleOnKeyDown(e)}>` ...here i have custom component that has input value.. `
    ` Does someone know why what might be the problem?
    – picaCHuXX Jul 30 '21 at 07:41
  • +1 to @Muppet for removing the outline too: import styled from "styled-components" const KeyReceiver = styled.div` &:focus { outline: none; } ` – MrOli3000 Oct 29 '22 at 01:55
33

You need to write it this way

<div 
    className="player"
    style={{ position: "absolute" }}
    onKeyDown={this.onKeyPressed}
    tabIndex="0"
  >

If onKeyPressed is not bound to this, then try to rewrite it using arrow function or bind it in the component constructor.

Panther
  • 8,938
  • 3
  • 23
  • 34
  • 4
    Sorry. I was sure, I just overlooked the this. But this is not the problem. It's still not working. – Michael Apr 20 '17 at 13:21
  • 1
    updated answer. You should either click on the div or bring it to focus before pressing any keys. – Panther Apr 20 '17 at 13:29
  • I did. It's not working. I updated the code above. It works fine with the DOM commands, but not in React style. – Michael Apr 20 '17 at 17:38
  • can you explain what is not working? is your function not getting callled or `console.log` is outputing nothing ? – Panther Apr 21 '17 at 03:52
  • The function is not called. I have a codepen: http://codepen.io/lafisrap/pen/OmyBYG . It about lines 275 and 307. – Michael Apr 21 '17 at 06:42
  • 1
    works for me with tabIndex={0} , tabIndex requires a number, not a string, so tabIndex="0" was flagged as a typescript error – busybee Nov 04 '20 at 07:12
11

You're thinking too much in pure Javascript. Get rid of your listeners on those React lifecycle methods and use event.key instead of event.keyCode (because this is not a JS event object, it is a React SyntheticEvent). Your entire component could be as simple as this (assuming you haven't bound your methods in a constructor).

onKeyPressed(e) {
  console.log(e.key);
}

render() {
  let player = this.props.boards.dungeons[this.props.boards.currentBoard].player;
  return (
    <div 
      className="player"
      style={{ position: "absolute" }}
      onKeyDown={this.onKeyPressed}
    >
      <div className="light-circle">
        <div className="image-wrapper">
          <img src={IMG_URL+player.img} />
        </div>
      </div>
    </div>
  )
}
steel
  • 11,883
  • 7
  • 72
  • 109
  • That's exactly how I wanted to write it. But the hell, it's not going. Here is the codepen: http://codepen.io/lafisrap/pen/OmyBYG . If you comment line 275 then key board input (cursor keys) is not working. onKeyDown is in line 307. – Michael Apr 21 '17 at 06:33
  • keyCode I use for cursor keys, as they return no character. – Michael Apr 21 '17 at 06:41
  • 1
    React doesn't use Javascript event objects, so there is no keyCode property. That `event` object is a React [SyntheticEvent](https://facebook.github.io/react/docs/events.html). – steel Apr 21 '17 at 16:11
  • @Michael I also am not entirely sure how you are triggering a key event on a div, but when I put this code into a fiddle with an `onClick` prop instead of `onKeyDown` the handler fires. – steel Apr 21 '17 at 17:08
  • 1
    Thanks. That explains it ... The key events work in input fields though. – Michael Apr 22 '17 at 10:36
8

Using the div trick with tab_index="0" or tabIndex="-1" works, but any time the user is focusing a view that's not an element, you get an ugly focus-outline on the entire website. This can be fixed by setting the CSS for the div to use outline: none in the focus.

Here's the implementation with styled components:

import styled from "styled-components"

const KeyReceiver = styled.div`
  &:focus {
    outline: none;
  }
`

and in the App class:

  render() {
    return (      
      <KeyReceiver onKeyDown={this.handleKeyPress} tabIndex={-1}>
          Display stuff...
      </KeyReceiver>
    )
Muppet
  • 5,767
  • 6
  • 29
  • 39
7

Also, remember that this trick will only work when focus is set on your div. If you want to manage keypress as soon as the div pops up, you can use this trick (especially useful for Drawers/Modals)

Pierre Olivier Tran
  • 1,669
  • 4
  • 24
  • 40
4

The answer with

<div 
    className="player"
    onKeyDown={this.onKeyPressed}
    tabIndex={0}
>

works for me, please note that the tabIndex requires a number, not a string, so tabIndex="0" doesn't work.

busybee
  • 119
  • 1
  • 5
0

For anyone else having issues with this I was losing the plot with on key down and on key up not working you can use mouse events.

This was fine for what I needed.

import './App.css'

function App() {
  const handleDown = () => {
    console.log('down')
  }
  const handleUp = () => {
    console.log('up')
  }
  return (
    <button onMouseDown={handleDown} onMouseUp={handleUp}>
      PRESS DOWN
    </button>
  )
}

export default App
user1503606
  • 3,872
  • 13
  • 44
  • 78