Lightweight keydown wrapper for React components

Last update: May 7, 2022

npm version dependencies

Use react-keydown as a higher-order component or decorator to pass keydown events to the wrapped component, or call methods directly via designated keys. Good for implementing keyboard navigation or other shortcuts.

Key advantages:

  • Declarative syntax: Components say what keys they will respond to.
  • Intuitive DX: Decorate a class or method to bind it to specified keys.
  • Scoping: Designate the scope of your bindings by decorating/wrapping components. Only those components and their children will receive the designated key events, and then only when they appear to be active.
  • Modifier keys: Support for standard modifier key combinations.
  • Lightweight: 2kb compressed and gzipped, and only attaches a single keydown listener to document, no matter how many keybindings you specify.
  • Cross-browser: Works in all browsers except IE 8 and below.

Consult the API & Reference Documentation or continue reading below for quick start.

NOTE: react-keydown doesn't use decorators itself, but to use the @keydown pattern in your application you will need a transpiler like Babel and a decorator transform plugin like this: https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy.

Install

npm install --save react-keydown

Use

The default build of react-keydown uses the CommonJS module system. For AMD or other support, use the umd-specific branch instead.

For methods: Decorate with keys that should trigger method

import React from 'react';
import keydown from 'react-keydown';

class MyComponent extends React.Component {

  @keydown( 'enter' ) // or specify `which` code directly, in this case 13
  submit( event ) {
    // do something, or not, with the keydown event, maybe event.preventDefault()
    MyApi.post( this.state );
  }
}

Note: Since the only context we have for keydown events is the component, decorated methods receive the event as their sole argument and the component instance as context.

Specify multiple keys that should trigger the method

import keydown, { Keys } from 'react-keydown';

const { ENTER, TAB } = Keys; // optionally get key codes from Keys lib to check against later

@keydown( ENTER, TAB, 'ctrl+z' ) // could also be an array
autocomplete( event ) {
  if ( event.which === ENTER ) { ... }
  MyApi.get( this.state );
}

For classes: Pass keydown events into your component

@keydown
class MyComponent extends React.Component {
  componentWillReceiveProps( { keydown } ) {
    if ( keydown.event ) {
      // inspect the keydown event and decide what to do
      console.log( keydown.event.which );
    }
  }

  render() {
    return (
      <div>keydown events will only get passed down after this DOM node mounts or is clicked on</div>
    );
  }
}

export default MyComponent;

Monitor only key codes which you care about:

const KEYS = [ 'shift+up', 'shift+down', 'enter', 'j', 'k', 'h', 'l' ];

@keydown( KEYS )
class MyComponent extends React.Component {
  ...
}

Use the @keydownScoped shortcut

When using the class decorator/higher-order component, decorate methods with @keydownScoped to identify the keydown.event prop as it comes in and bind certain values to methods:

import keydown, { keydownScoped } from 'react-keydown';

@keydown( 'enter' ) // optional to specify a key here. if called with just @keydown, all key events will get passed down
class MyComponent extends React.Component {
  render() {
    return <MyOtherComponent {...this.props} />;
  }
}

class MyOtherComponent extends React.Component {
  ...
  @keydownScoped( 'enter' ) // inspects nextProps.keydown.event in componentWillReceiveProps behind the scenes
  submit() {
    // submit
  }
}

This is a convenience method, but also lets you specify a larger view context where this key binding should be active. Sometimes the component where the binding is declared is too small on its own.

This can also be a good way to set up app-wide shortcuts. Wrap your root component with @keydown and then use @keydownScoped or manually inspect the keydown.event props in the child components where those bindings are relevant.

Handling all keys

In some cases you might want to handle all keys on your own. For that, you can specify the following:

import keydown, { ALL_KEYS } from 'react-keydown'

@keydown( ALL_KEYS )
handleKeys(ev) {
  // handle keys here
}

Handling all printable keys

Another useful feature is handling all printable characters.

import keydown, { ALL_PRINTABLE_KEYS } from 'react-keydown'

@keydown( ALL_PRINTABLE_KEYS )
beginEdit(ev) {
  // Start editing
}

Caveat: Input, textarea, and select elements

By default, bindings will not work when these fields have focus, in order not to interfere with user input and shortcuts related to these controls. You can override this in two ways:

  1. Give your shortcut a ctrl modifier.

  2. Since v1.6.0, there is experimental support for adding an onKeyDown binding to the element, specifying a method decorated with @keydown as the handler. For example:

class MyClass extends React.Component {

  @keydown( 'a' )
  myMethod( event ) {
    console.log( event ); // should log only on 'a' keystroke, whether input is focused or not
  }

  render() {
    return <input onKeyDown={ this.myMethod } />;
  }
}

In the second case you could make multiple inputs work this way by spreading { onKeyDown: this.myMethod } into them, or by making this a reusable input component that takes the method as a prop (or composes multiple methods passed in as props).

Demo

Go to the live demo or:

$ open example/public/index.html

Note that this is very much a work in progress!

Test

$ npm test

Notes, disclaimers, and tips

  • The decorator pattern @keydown currently requires transpilation by the Babel legacy decorators transform or the equivalent.
  • Components that have a React 16 fragment at their root may not be activated properly when clicked. See this issue for more detail.
  • The default build outputs CommonJS modules and native ES modules. For AMD or other support, use the umd-specific branch instead.
  • This lib has only been tested using ES2015 classes and class methods. Some method decoration functionality may work on other types of object methods.
  • Duplicate keybindings for components that are mounted at the same time will not both fire. The more recently mounted component, or the one that has been focused or clicked most recently, will win. If you do want both to fire, decorate a common ancestor class with @keydown( ... ) and then use @keydownScoped( ... ) in the child components (or just inspect nextProps.keydown.event in both).
  • Since the only context we have for keydown events is the component, decorated methods receive the event as their sole argument and the component instance as context.
  • The method decorators wrap React lifecycle methods in order to work as seamlessly and efficiently as possible. The class decorator does not do this, functioning instead as a higher-order component.

Questions

Why is this so limited, only working on keydown and such?

I published this out of my particular need on a project. If anyone else ever arrives here and needs something else let me know via issues or a pull request.

GitHub

https://github.com/glortho/react-keydown
Comments
  • 1. Make it work with Babel 6

    Currently it is working with Babel 5 decorators support, but something happens in babel-plugin-transform-decorators-legacy.

    I get an error: image

    Using https://github.com/christianalfoni/webpack-express-boilerplate as a basis with babel-plugin-transform-decorators-legacy.

    Reviewed by kmetsalu at 2016-01-28 15:22
  • 2. Bound components lose focus when a click target is removed from the DOM

    The issue I have is similar to https://github.com/glortho/react-keydown/issues/16, but not sure.

    When I decorate my class with @keydown() without any arguments, I get error as

    image

    The reason I'm passing no args is that I want to capture all keys and I believe its decided here.

    Am I doing something wrong here? or its a compatibility bug like #16?

    Reviewed by salmanm at 2017-07-24 18:15
  • 3. Processing Enter (for example) on a form

    I have a form (though I'm not using html native <form>), and I'm trying to process the Enter key. I'm decorating my method with @keydown('enter') but this only works if focus isn't in an input field. (Though it works fine if I use 'ctrl+enter' instead).

    Is there a nice way to set forceConsider so that _shouldConsiderwill let it through even when focus is on an input field? Seems like I need a @keydownForAnyFocus decorator or something similar...

    Great library! Thanks.

    Reviewed by thunderkid at 2017-11-02 18:54
  • 4. support keydownGlobal

    WIP or POC

    I don't have unit test at this stage, but initial testing says it is good.

    This is to address issue #69

    Also, formattring kinda screw up, but if you are happy with the concept, I can fix these trivial issues :)

    Reviewed by sheldonrong at 2017-09-25 07:28
  • 5. Can't use keydown on components which use portals

    Hey :) I have a modal component which renders some markup in a portal (so it overlays the whole body). I wanted to use react-keydown to close the overlay when the ESC key is pressed. This is not working. I believe that the problem is that react-keydown only listens on keydowns which happen inside the component. Through react-portal the overlay jumps out of the current component. What would be needed is to somehow listen to all keydowns of the whole window.

    Reviewed by mmsbrggr at 2017-08-24 14:35
  • 6. findDOMNode on unmounted component

    Hi, I found a bug in your plugin.

    From time to time it happens that a component is mounted and directly unmounted. When this component has a keydown annotation somewhere, it will be added to the store after it was unmounted. If you click then somewhere the _onClick function tries to find this unmounted component. This causes an Error in IE -> findDOMNode was called on an unmounted component.

    The issue is the setTimeout in the onMount function.

    function onMount( instance ) {
      // have to bump this to next event loop because component mounting routinely
      // preceeds the dom click event that triggered the mount (wtf?)
      setTimeout(() => store.activate( instance ), 0);
      listeners.bindKeys( _onKeyDown );
      listeners.bindClicks( _onClick );
      domHelpers.bindFocusables( instance, store.activate );
    }
    

    So one solution would be that you remove the setTimeout and call the store.activate directly. Or you could also delay the onUnmount with a setTimeout so that the activate is called before the unmount.

    Reviewed by lemonbutter at 2017-08-17 14:51
  • 7. Symbol is undefined in Internet Explorer

    Reviewed by nperriwp at 2017-08-21 20:38
  • 8. Having trouble getting this working in a este project (https://github.com/este/este)

    I'm new to react so I could be doing something wrong. I'm trying to use the method decorator pattern which seems pretty straight forward, but my method is never firing. I stepped through some of your project code and from what I can tell in method_decorator.js (dist version) line 35, "var componentDidMount = target.componentDidMount;" is receiving undefined.

    I realize this isn't much info, what can I do to debug this issue?

    Reviewed by jdart at 2015-10-30 03:12
  • 9. Make compatible to react 0.14.0

    Hi!

    Since react 0.14.0 there is an error when I decorate a method in a react component with @keydown(key) (react-keydown 1.3.6):

    class_decorator.js:19 Uncaught TypeError: Cannot call a class as a function

    Worked for me with react 0.14.0-rc1.

    Reviewed by kuuup at 2015-10-14 07:04
  • 10. 1.7.5 update throws `.isAttached is not a function` errors

    After upgrading from 1.7.4 to 1.7.5 I've been getting these errors from all components that use react-keydown:

    event_handlers.js:37 Uncaught TypeError: __WEBPACK_IMPORTED_MODULE_0__lib_dom_helpers__.a.isAttached is not a function
        at HTMLDocument._onClick (event_handlers.js:37)
    _onClick @ event_handlers.js:37
    
    Reviewed by EvHaus at 2017-07-25 15:19
  • 11. Not working keydown when it nested multilevel

    <MyComp1 having keydown>
     <1>
    .
    .
     <n> having keydown scoped </n>
    .
    .
    </1>
    </MyComp>
    
    

    In this scenario keydown is not working

    Reviewed by ashokkumarsand at 2017-03-23 06:39
  • 12. Alt+Numpad

    Hi,

    any chance to set Alt + Numpad key for binding? This does not seem to work:

    setBinding({
      target: X.prototype,
      fn: X.prototype.method,
      keys: ['alt+103']
    });
    
    Reviewed by alexdemartos at 2021-10-01 07:30
  • 13. componentWillReceiveProps will be deprecated

    According to React, using componentWillReceiveProps() is a bad idea and it will be deprecated soon, version 17 seems to be the last supporting the method with that name.

    When using keydown for classes, what should be the proper way to handle the key events?

    Reviewed by iqqmuT at 2018-09-24 09:42
  • 14. Not working in ie 11

    Hi, I found this project very interesting and I start coding a simple example with Internet Explorer 11. The array.from polyfill does not work if the object is a Set. I found problems with Symbol and Map too. A quick fix is to replace lib\array.from with something like

    if (!Array.from) { Array.from = function () {

        // The length property of the from method is 1.
        return function from(arrayLike /*, mapFn, thisArg */) {
            let retArray = [];
            arrayLike.forEach(function (v) { retArray.push(v) });
            return retArray;
        };
    }();
    

    }

    and replace symbol.polyfill.js with import 'core-js/es6/symbol'; import "core-js/fn/symbol/iterator.js"; import 'core-js/es6/map';

    I imported symbol.polyfill.js into store.js and keys.js

    Any chance to have a newer version of the package with this fix_ Thanks

    Reviewed by AkumaTen at 2018-04-23 12:54
  • 15. Add flow types definitions

    I just started looking at Flow and while using your library (great work!) I noticed this library lacks the types definitions. Would it be possible to add them?

    Reviewed by ste93cry at 2017-11-22 20:52
  • 16. How can "forceConsider" be set to true?

    I need to set forceConsider argument to _onKeyDown function in event_handler.js to true. How can this be done?

    The reason I need that is that I need to be able to cancel an action with an escape key, regardless of where the target is. In my case, the target sometimes falls into the black list of _shouldConsider function, and then my code doesn't work.

    Thanks a lot.

    Reviewed by valp124 at 2017-09-22 19:00
Related tags
React higher order component for adding onEnterKeyDown to input components

React higher order component for adding onEnterKeyDown to input components

May 4, 2020
Declarative hotkey and focus area management for React

React HotKeys A declarative library for handling hotkeys and focus areas in React applications. Upgrading from 1.*.* ? See the upgrade notes. Looking

May 12, 2022
React component to handle keyboard events :key:

react-key-handler ?? React component to handle keyboard events (such as keyup, keydown & keypress). Testimonials “Happy to see that react-key-handler

Apr 18, 2022
🐭 React hook that tracks mouse events on selected element - zero dependencies
🐭  React hook that tracks mouse events on selected element - zero dependencies

React Mighty Mouse React hook that tracks mouse events on selected element. Demo Demos created with React DemoTab ?? Install npm install react-hook-mi

May 10, 2022
An easy-to-use keyboard event react component, Package size less than 3kb
An easy-to-use keyboard event react component, Package size less than 3kb

An easy-to-use keyboard event react component, Can achieve a variety of custom keyboard functions, Package size less than 3kb

May 5, 2022
Niue is a tiny shared state and event library for React

Niue is a small library (~1.3kb before compression) that provides a simple way to manage your React app's shared state and send events between components.

May 16, 2022
Blend-mode-demo-using-react-p5-wrapper - React+p5.js app demonstrating p5.js blend modes, Uses react-p5-wrapper
Blend-mode-demo-using-react-p5-wrapper - React+p5.js app demonstrating p5.js blend modes, Uses react-p5-wrapper

React + p5.js blend mode demonstrator This is a React + p5.js app which demonstr

Jan 29, 2022
React wrapper for lightweight WYSIWYG editor Trumbowyg
React wrapper for lightweight WYSIWYG editor Trumbowyg

React-Trumbowyg React wrapper for trumbowyg. If you ❤️ library, please star it and upvote it on awesome-react-components Table of contents How do I ad

Jan 17, 2022
A lightweight react wrapper for creating fluid galleries as seen on Flickr and Google Images
A lightweight react wrapper for creating fluid galleries as seen on Flickr and Google Images

A lightweight react wrapper for creating fluid galleries as seen on Flickr and Google Images, based on Pixabay/JavaScript-flexImages

Apr 7, 2022
⛳️ Apache ECharts components for React wrapper. 一个简单的 Apache echarts 的 React 封装。

echarts-for-react The simplest, and the best React wrapper for Apache ECharts. Install $ npm install --save echarts-for-react # `echarts` is the peer

May 12, 2022
An inline wrapper for calling out React Native components via tooltip
An inline wrapper for calling out React Native components via tooltip

React Native Walkthrough Tooltip React Native Walkthrough Tooltip is a fullscreen modal that highlights whichever element it wraps. When not visible,

May 9, 2022
Frappe Gantt components for React wrapper.
Frappe Gantt components for React wrapper.

gantt-for-react Frappe Gantt component for React wrapper. 1. Install npm install --save gantt-for-react 2. Usage Online demo see https://git.hust.cc/g

Apr 12, 2022
React wrapper components for smooth-dnd

react-smooth-dnd A fast and lightweight drag&drop, sortable library for React with many configuration options covering many d&d scenarios. It uses css

May 10, 2022
react-disqus-components wrapper for official disqus web api

react-disqus-components React Functional Component with disqus webapi integration. Usage Only support React Functional Component Required Props See Di

Mar 9, 2022
A Formsy compatibility wrapper for Material-UI form components

⚠️ Help Wanted ⚠️ This package is currently under active restoration after a long hiatus, and all help is appreciated, especially MUI users. formsy-ma

Mar 17, 2022
Syncfusion React UI components library offer more than 50+ cross-browser, responsive, and lightweight react UI controls for building modern web applications.

Syncfusion React UI components library offer more than 50+ cross-browser, responsive, and lightweight react UI controls for building modern web applications.

May 16, 2022
👀 Easily apply tilt hover effect on React components - lightweight/zero dependencies
👀  Easily apply tilt hover effect on React components - lightweight/zero dependencies

React Tilt ?? Easily apply tilt hover effect on React components Demo Demos created with React DemoTab ?? Install npm install react-parallax-tilt Feat

May 11, 2022
Lightweight React Native UI Components inspired on Vant

vant-react-native Install yarn add vant-react-native Or npm install vant-react-native Usage import React

Mar 31, 2022
Lightweight auth library based on oidc-client for React single page applications (SPA). Support for hooks and higher-order components (HOC).

Lightweight auth library based on oidc-client for React single page applications (SPA). Support for hooks and higher-order components (HOC).

May 10, 2022
👀 Easily apply tilt hover effect on React components - lightweight/zero dependencies
👀  Easily apply tilt hover effect on React components - lightweight/zero dependencies

React Tilt ?? Easily apply tilt hover effect on React components Demo ?? Install npm install react-parallax-tilt Features Lightweight (3.8kB), zero de

May 11, 2022