Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Element-bound events with useOnEscapePress are triggering all escape events globally #1854

Closed
iansan5653 opened this issue Feb 10, 2022 · 1 comment · Fixed by #1861
Closed
Assignees
Labels
bug Something isn't working react

Comments

@iansan5653
Copy link
Contributor

iansan5653 commented Feb 10, 2022

A change introduced in 34.4.0 was to add support for useOnEscapePress to bind events to specific elements instead of just the document root (and to implement that new functionality in the useOverlay hook):

#1824 4eab65e5 Thanks @siddharthkp! - Overlay: Attach escape handler to overlay container instead of document to fix stopPropagation

This makes sense, because the events need to be bound to the overlay container so that the overlay can stopPropogation and keep its events contained.

However, this hook is using a global function with a global array of event handlers, even with the element-bound events. So when an escape event is triggered on a top-level overlay, it still calls every escape event handler ever registered:

const handlers: ((e: KeyboardEvent) => void)[] = []
/**
* Calls all handlers in reverse order
* @param event The KeyboardEvent generated by the Escape keydown.
*/
function handleEscape(event: KeyboardEvent) {
if (event.key === 'Escape' && !event.defaultPrevented) {
for (let i = handlers.length - 1; i >= 0; --i) {
if (typeof handlers[i] === 'function') handlers[i](event)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (event.defaultPrevented) {
break
}
}
}
}

An example of the undesirable behavior that this bug causes is visible in the Nested Overlays story:

  • Click the dropdown arrow next to "Star"
  • Click "+ Create List"
  • Press Escape
    • Only the top overlay should close, but both do instead
  • Click the dropdown arrow next to "Star" again
    • Both overlays open in incorrect order because the top overlay was never cleaned up properly

I see two possible ways to fix this:

  1. For element-bound useOnEscapePress events, bind/unbind the passed handler function directly. This seems simple but I'm not sure why the behavior is the way it is in the first place - there may be a reason for it. Example of this change:
    if (element) element.addEventListener('keydown', escapeCallback)
    else document.addEventListener('keydown', escapeCallback)
    return () => {
      if (element) element.removeEventListener('keydown', escapeCallback)
      else document.removeEventListener('keydown', escapeCallback)
    }
  1. Or, only use useOnEscapePress for global (root-bound) events, and just bind the escape event using react's native onKeyPress property instead.

cc @siddharthkp @mattcosta7

@iansan5653 iansan5653 changed the title Element-bound 'escape' events with useOnEscapePress are triggering all escape events Element-bound events with useOnEscapePress are triggering all escape events globally Feb 10, 2022
@siddharthkp siddharthkp added bug Something isn't working react labels Feb 10, 2022
@siddharthkp
Copy link
Member

@iansan5653 #1861 Should take care of this! I've added a story if you want to see the new behavior: https://primer-react-jhga7sawh-primer.vercel.app/react/storybook?path=/story/internal-components-overlay--memex-nested-overlays

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working react
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants