[BUG] Hotkey handlers ignored when not present on initial load
Describe the bug
When my <HotKeys></HotKeys> tag is dynamically added to the UI, its handlers are getting ignored.
How are you using react hotkeys components? (HotKeys, GlobalHotKeys, IgnoreKeys etc)
Here is a trimmed down version of what I'm doing
render() {
if (this.props.isOpen) {
return <HotKeys keyMap={keyMap()} handlers={handlers()}>...</HotKeys>
}
return <div/>
}
Expected behavior Hotkey handlers for component in focus are triggered
Platform (please complete the following information):
- 2.0.0
- Chrome
- iOS
Are you willing and able to create a PR request to fix this issue? Yes, if given a little guidance
Include the smallest log that includes your issue:
HotKeys (F5๐-C1โญ๏ธ-P0๐บ:) Focused.
FocusOnlyKeyEventStrategy.js:156 HotKeys (F5๐-C1โญ๏ธ-P0๐บ:) Component options:
{
"actions": {
"OPEN_FUZZY_SEARCH_FILE": [
{
"prefix": "",
"actionName": "OPEN_FUZZY_SEARCH_FILE",
"sequenceLength": 1,
"id": "Enter",
"keyDictionary": {
"Enter": true
},
"keyEventType": 0,
"size": 1
}
],
"UP_FUZZY_SEARCH_FILE_LIST": [
{
"prefix": "",
"actionName": "UP_FUZZY_SEARCH_FILE_LIST",
"sequenceLength": 1,
"id": "ArrowUp",
"keyDictionary": {
"ArrowUp": true
},
"keyEventType": 0,
"size": 1
}
],
"DOWN_FUZZY_SEARCH_FILE_LIST": [
{
"prefix": "",
"actionName": "DOWN_FUZZY_SEARCH_FILE_LIST",
"sequenceLength": 1,
"id": "ArrowDown",
"keyDictionary": {
"ArrowDown": true
},
"keyEventType": 0,
"size": 1
}
]
},
"handlers": {
"OPEN_FUZZY_SEARCH_FILE": "function () { [native code] }",
"UP_FUZZY_SEARCH_FILE_LIST": "function () { [native code] }",
"DOWN_FUZZY_SEARCH_FILE_LIST": "function () { [native code] }"
},
"componentId": 1,
"options": {
"defaultKeyEvent": "keydown"
}
}
VM1150:1 Bad: [object Object]
GlobalKeyEventStrategy.js:310 HotKeys (GLOBAL-E38๐): New 'c' keydown event (that has NOT passed through React app).
GlobalKeyEventStrategy.js:503 HotKeys (GLOBAL-E38๐): Added 'c' to current combination: 'c'.
GlobalKeyEventStrategy.js:506 HotKeys (GLOBAL-E38๐): Key history: [
{
"keys": {
"c": [
[
0,
0,
0
],
[
1,
0,
0
]
]
},
"ids": [
"c"
],
"keyAliases": {}
}
].
GlobalKeyEventStrategy.js:557 HotKeys (GLOBAL-E38๐): Attempting to find action matching 'c' keydown . . .
AbstractKeyEventStrategy.js:405 HotKeys (GLOBAL-E38๐-C0๐บ): Internal key mapping:
{
"": {
"actionConfigs": {
"Control+p": {
"prefix": "",
"sequenceLength": 1,
"id": "Control+p",
"keyDictionary": {
"Control": true,
"p": true
},
"size": 2,
"events": {
"0": {
"actionName": "OPEN_FUZZY_SEARCH_DIALOG",
"handler": "function () { [native code] }"
}
}
}
},
"order": [
"Control+p"
]
}
}
AbstractKeyEventStrategy.js:429 HotKeys (GLOBAL-E38๐-C0๐บ): No matching actions found for 'c' keydown.
FocusOnlyKeyEventStrategy.js:298 HotKeys (F5๐-E39๐-C1โญ๏ธ-P0๐บ:) New 'c' keydown event.
FocusOnlyKeyEventStrategy.js:533 HotKeys (F5๐-E39๐-C1โญ๏ธ-P0๐บ:) Added 'c' to current combination: 'c'.
FocusOnlyKeyEventStrategy.js:538 HotKeys (F5๐-E39๐-C1โญ๏ธ-P0๐บ:) Key history: [
{
"keys": {
"c": [
[
0,
0,
0
],
[
1,
0,
0
]
]
},
"ids": [
"c"
],
"keyAliases": {}
}
].
FocusOnlyKeyEventStrategy.js:648 HotKeys (F5๐-E39๐-C1โญ๏ธ-P0๐บ:) Attempting to find action matching 'c' keydown . . .
AbstractKeyEventStrategy.js:405 HotKeys (F5๐-E39๐-C0๐บ) Internal key mapping:
{
"": {
"actionConfigs": {
"Enter": {
"prefix": "",
"sequenceLength": 1,
"id": "Enter",
"keyDictionary": {
"Enter": true
},
"size": 1,
"events": {
"0": {
"actionName": "OPEN_FUZZY_SEARCH_FILE",
"handler": "function () { [native code] }"
}
}
},
"ArrowUp": {
"prefix": "",
"sequenceLength": 1,
"id": "ArrowUp",
"keyDictionary": {
"ArrowUp": true
},
"size": 1,
"events": {
"0": {
"actionName": "UP_FUZZY_SEARCH_FILE_LIST",
"handler": "function () { [native code] }"
}
}
},
"ArrowDown": {
"prefix": "",
"sequenceLength": 1,
"id": "ArrowDown",
"keyDictionary": {
"ArrowDown": true
},
"size": 1,
"events": {
"0": {
"actionName": "DOWN_FUZZY_SEARCH_FILE_LIST",
"handler": "function () { [native code] }"
}
}
}
},
"order": null
}
}
AbstractKeyEventStrategy.js:429 HotKeys (F5๐-E39๐-C0๐บ) No matching actions found for 'c' keydown.
GlobalKeyEventStrategy.js:310 HotKeys (GLOBAL-E40๐): New 'c' keypress event (that has NOT passed through React app).
GlobalKeyEventStrategy.js:506 HotKeys (GLOBAL-E40๐): Key history: [
{
"keys": {
"c": [
[
1,
0,
0
],
[
1,
1,
0
]
]
},
"ids": [
"c"
],
"keyAliases": {}
}
].
GlobalKeyEventStrategy.js:547 HotKeys (GLOBAL-E40๐): Ignored 'c' keypress because it doesn't have any keypress handlers.
FocusOnlyKeyEventStrategy.js:339 HotKeys (F5๐-E40๐-C1โญ๏ธ-P0๐บ:) Ignored 'c' keypress as it was not expected, and has already been simulated.
EventPropagator.js:252 HotKeys (F5๐-E40๐-Cnull๐บ) Stopping further event propagation.
GlobalKeyEventStrategy.js:161 HotKeys (GLOBAL-C0๐บ): Global component 0 updated.
GlobalKeyEventStrategy.js:164 HotKeys (GLOBAL-C0๐บ): Component options:
{
"actions": {
"OPEN_FUZZY_SEARCH_DIALOG": [
{
"prefix": "",
"actionName": "OPEN_FUZZY_SEARCH_DIALOG",
"sequenceLength": 1,
"id": "Control+p",
"keyDictionary": {
"Control": true,
"p": true
},
"keyEventType": 0,
"size": 2
}
]
},
"handlers": {
"OPEN_FUZZY_SEARCH_DIALOG": "function () { [native code] }"
},
"componentId": 0,
"options": {
"defaultKeyEvent": "keydown"
}
}
GlobalKeyEventStrategy.js:161 HotKeys (GLOBAL-C0๐บ): Global component 0 updated.
GlobalKeyEventStrategy.js:164 HotKeys (GLOBAL-C0๐บ): Component options:
{
"actions": {
"OPEN_FUZZY_SEARCH_DIALOG": [
{
"prefix": "",
"actionName": "OPEN_FUZZY_SEARCH_DIALOG",
"sequenceLength": 1,
"id": "Control+p",
"keyDictionary": {
"Control": true,
"p": true
},
"keyEventType": 0,
"size": 2
}
]
},
"handlers": {
"OPEN_FUZZY_SEARCH_DIALOG": "function () { [native code] }"
},
"componentId": 0,
"options": {
"defaultKeyEvent": "keydown"
}
}
GlobalKeyEventStrategy.js:310 HotKeys (GLOBAL-E41๐งก): New 'c' keyup event (that has NOT passed through React app).
GlobalKeyEventStrategy.js:506 HotKeys (GLOBAL-E41๐งก): Key history: [
{
"keys": {
"c": [
[
1,
1,
0
],
[
1,
1,
1
]
]
},
"ids": [
"c"
],
"keyAliases": {}
}
].
GlobalKeyEventStrategy.js:547 HotKeys (GLOBAL-E41๐งก): Ignored 'c' keyup because it doesn't have any keyup handlers.
GlobalKeyEventStrategy.js:310 HotKeys (GLOBAL-E42โค๏ธ): New 'ArrowDown' keydown event (that has NOT passed through React app).
GlobalKeyEventStrategy.js:494 HotKeys (GLOBAL-E42โค๏ธ): Started a new combination with 'ArrowDown'.
GlobalKeyEventStrategy.js:495 HotKeys (GLOBAL-E42โค๏ธ): Key history: [
{
"keys": {
"c": [
[
1,
1,
0
],
[
1,
1,
1
]
]
},
"ids": [
"c"
],
"keyAliases": {}
},
{
"keys": {
"ArrowDown": [
[
0,
0,
0
],
[
1,
0,
0
]
]
},
"ids": [
"ArrowDown"
],
"keyAliases": {}
}
].
GlobalKeyEventStrategy.js:557 HotKeys (GLOBAL-E42โค๏ธ): Attempting to find action matching 'ArrowDown' keydown . . .
AbstractKeyEventStrategy.js:405 HotKeys (GLOBAL-E42โค๏ธ-C0๐บ): Internal key mapping:
{
"": {
"actionConfigs": {
"Control+p": {
"prefix": "",
"sequenceLength": 1,
"id": "Control+p",
"keyDictionary": {
"Control": true,
"p": true
},
"size": 2,
"events": {
"0": {
"actionName": "OPEN_FUZZY_SEARCH_DIALOG",
"handler": "function () { [native code] }"
}
}
}
},
"order": null
}
}
AbstractKeyEventStrategy.js:429 HotKeys (GLOBAL-E42โค๏ธ-C0๐บ): No matching actions found for 'ArrowDown' keydown.
GlobalKeyEventStrategy.js:310 HotKeys (GLOBAL-E43๐): New 'ArrowDown' keyup event (that has NOT passed through React app).
GlobalKeyEventStrategy.js:506 HotKeys (GLOBAL-E43๐): Key history: [
{
"keys": {
"c": [
[
1,
1,
0
],
[
1,
1,
1
]
]
},
"ids": [
"c"
],
"keyAliases": {}
},
{
"keys": {
"ArrowDown": [
[
1,
0,
0
],
[
1,
0,
1
]
]
},
"ids": [
"ArrowDown"
],
"keyAliases": {}
}
].
GlobalKeyEventStrategy.js:547 HotKeys (GLOBAL-E43๐): Ignored 'ArrowDown' keyup because it doesn't have any keyup handlers.
I've found that in another place, when I see "Ignored
What Configuration options are you using?
configure({
/**
* The level of logging of its own behaviour React HotKeys should perform.
*/
logLevel: 'verbose',
/**
* Default key event key maps are bound to (keydown|keypress|keyup)
*/
defaultKeyEvent: 'keydown',
/**
* The default component type to wrap HotKey components' children in, to provide
* the required focus and keyboard event listening for HotKeys to function
*/
defaultComponent: 'div',
/**
* The default tabIndex value passed to the wrapping component used to contain
* HotKey components' children. -1 skips focusing the element when tabbing through
* the DOM, but allows focusing programmatically.
*/
defaultTabIndex: '-1',
/**
* The HTML tags that React HotKeys should ignore key events from. This only works
* if you are using the default ignoreEventsCondition function.
* @type {String[]}
*/
ignoreTags: [],
/**
* The function used to determine whether a key event should be ignored by React
* Hotkeys. By default, keyboard events originating elements with a tag name in
* ignoreTags, or a isContentEditable property of true, are ignored.
*
* @type {Function<KeyboardEvent>}
*/
ignoreEventsCondition: (e) => {
return false;
},
/**
* Whether to ignore changes to keyMap and handlers props by default
* (this reduces a significant amount of unnecessarily resetting
* internal state)
* @type {boolean}
*/
ignoreKeymapAndHandlerChangesByDefault: false,
/**
* Whether to ignore repeated keyboard events when a key is being held down
* @type {boolean}
*/
ignoreRepeatedEventsWhenKeyHeldDown: true,
/**
* Whether React HotKeys should simulate keypress events for the keys that do not
* natively emit them.
* @type {boolean}
*/
simulateMissingKeyPressEvents: false,
/**
* Whether to call stopPropagation() on events after they are
* handled (preventing the event from bubbling up any further, both within
* React Hotkeys and any other event listeners bound in React).
*
* This does not affect the behaviour of React Hotkeys, but rather what
* happens to the event once React Hotkeys is done with it (whether it's
* allowed to propagate any further through the Render tree).
*/
stopEventPropagationAfterHandling: true,
/**
* Whether to call stopPropagation() on events after they are
* ignored (preventing the event from bubbling up any further, both within
* React Hotkeys and any other event listeners bound in React).
*
* This does not affect the behaviour of React Hotkeys, but rather what
* happens to the event once React Hotkeys is done with it (whether it's
* allowed to propagate any further through the Render tree).
*/
stopEventPropagationAfterIgnoring: true,
/**
* Whether to allow combination submatches - e.g. if there is an action
* bound to cmd, pressing shift+cmd will *not* trigger that action when
* allowCombinationSubmatches is false.
*/
allowCombinationSubmatches: false,
/**
* A mapping of custom key codes to key names that you can then use in your
* key sequences
*/
customKeyCodes: {}
})
I had the same issue, I solved it using innerRef and useLayoutEffect
I hope this helps you:
import React, { useRef, useLayoutEffect } from 'react';
import { HotKeys } from 'react-hotkeys';
const FocusHotKeys: React.FC<Props> = ({ children, handlers, keyMap }) => {
const ref = useRef<HTMLDivElement>(null);
useLayoutEffect((): void => {
if (ref.current) {
ref.current.focus();
}
}, [ref]);
return (
<HotKeys innerRef={ref} handlers={handlers} keyMap={keyMap}>
{children}
</HotKeys>
);
};
export default FocusHotKeys;
type Props = {
handlers: Readonly<{ [key: string]: (keyEvent?: KeyboardEvent) => void }>;
keyMap: Readonly<{ [key: string]: string }>;
};
Thanks for posting your issue.
Unfortunately I do not have the time to actively work on this package, but I am seeking other active maintainers. If you are willing to create a pull request or help out, that would be an excellent way of moving this forward.