greena13 / react-hotkeys

Declarative hotkey and focus area management for React
https://github.com/greena13/react-hotkeys
ISC License
2.15k stars 160 forks source link

[BUG] Hotkey handlers ignored when not present on initial load #259

Open tjcarroll11 opened 4 years ago

tjcarroll11 commented 4 years ago

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):

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 as it was not expected, and has already been simulated.", my hotkeys all start failing. If I unfocus and refocus, it will fix it. In this case, it only fixes it for literally 1 keypress.

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: {}
    })
hellupline commented 4 years ago

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 }>;
};
greena13 commented 4 years ago

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.