Flow type 'keydown' event

Viewed 1866

In this code

document.addEventListener('keydown', (e: SyntheticKeyboardEvent<Document>) => {
    if (e.ctrlKey && e.shiftKey && e.code === 'KeyE' && !this.state.envChangePopupOpened) {
        this.openPopup();
    }
 });

Flow gives 2 issues which seem to not be as such.

First it gives this cryptic message:

Cannot call `document.addEventListener` because: Either string [1] is incompatible with enum [2]. Or string [1] is incompatible with enum [3]. Or `KeyboardEvent` [4] is incompatible with `SyntheticKeyboardEvent` [5] in the first argument. Or string [1] is incompatible with enum [6]. Or string [1] is incompatible with string literal `wheel` [7]. Or string [1] is incompatible with enum [8]. Or string [1] is incompatible with enum [9]. Or string [1] is incompatible with enum [10]. Or string [1] is incompatible with enum [11]. Or string [1] is incompatible with enum [12]. Or `Event` [13] is incompatible with `SyntheticKeyboardEvent` [5] in the first argument. (References: [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13])

and second gives:

property `code` is missing in `SyntheticKeyboardEvent` [1]. (References: [1])

But it should be on e.

How do you handle these?

EDIT:

The second issue turns out is not supported in SyntheticEvent and should be worked around.

1 Answers

The problem is that SyntheticKeyboardEvent is a type for events that are handled through React's event system. Because you are calling document.addEventListener directly you will not get a synthetic event; you will get a native event instead. The type for native keyboard events is KeyboardEvent which is defined in Flow's built-in DOM type definitions. You can change the event type like this:

document.addEventListener('keydown', (e: KeyboardEvent) => {
    if (e.ctrlKey && e.shiftKey && e.code === 'KeyE' && !this.state.envChangePopupOpened) {
        this.openPopup();
    }
 });

The KeyboardEvent type does have a code property.

To understand why the error message is so weird you can look at Flow's built-in definition for addEventListener, and see that it has many overloaded signatures. The reason for this is that event objects for different event types come with different properties. (The code and shiftKey properties are good examples.) The overloaded signatures for addEventListener relate specific event types to corresponding event object types. This is the overload you want to use:

addEventListener(
  type: KeyboardEventTypes,
  listener: KeyboardEventListener,
  optionsOrUseCapture?: EventListenerOptionsOrUseCapture
): void;

This is the definition for the keyboard event types:

type KeyboardEventTypes = 'keydown' | 'keyup' | 'keypress';

Each overloaded signature uses a similar enum type for its type parameter. You get a long, complicated error message because there is a type mismatch in one of the arguments, and Flow does not know for certain which of the many overloaded signatures of addEventListener is the one that you want. The messages saying string is incompatible with enum inform you that if you use a different argument for type you will get a different signature, and as far as Flow knows maybe that could fix the problem.

Of course using a different type argument is not the source of your problem. There is another clue in the error message, Or KeyboardEvent [4] is incompatible with SyntheticKeyboardEvent which points you to the proper event type for the event value in your callback.

In other circumstances you could remove the type annotation from the callback argument, and let Flow infer the correct type. But because addEventListener is overloaded Flow cannot infer the event type in this case.

Related