Search code examples
javascriptreactjstypescripteventsnext.js

What is the type of event handler attached to the window object in TypeScript?


I'm working on a Next.js app, and I found out that the type I assigned to the event throws me an error.

Here is my code so far:

const keyboard = useRef<HTMLDivElement>(null);

useEffect(() => {
    if (!showKeyboard) return; // If the keyboard is not shown then we don't need a listener to hide it, so we stop here.

    // Hide the keyboard if a click was triggered outside the keyboard.
    function hideKeyboard(event: Event) {
        if (
            keyboard.current &&
            event != null &&
            !keyboard.current.contains(event?.target)
        ) {
            setShowKeyboard(false);
        }
    }
    window.addEventListener('click', hideKeyboard);
    return () => window.removeEventListener('click', hideKeyboard); // Remove the listener.
}, [showKeyboard]);

The app runs and works as expected, however an error is thrown by TypeScript:

TS2345: Argument of type 'EventTarget | null' is not assignable to parameter of type 'Node | null'.<br/>Type 'EventTarget' is missing the following properties from type 'Node': baseURI, childNodes, firstChild, isConnected, and 43 more.

I have also tried React.MouseEvent<Window> and React.MouseEvent<HTMLElement>. However, the same error is thrown plus the next one:

No overload matches this call.   
Overload 1 of 2, '(type: "click", listener: (this: Window, ev: MouseEvent) => any, options?: boolean | EventListenerOptions | undefined): void', gave the following error.
Argument of type '(event: MouseEvent<HTMLElement, MouseEvent>) => void' is not assignable to parameter of type '(this: Window, ev: MouseEvent) => any'.       
    Types of parameters 'event' and 'ev' are incompatible.         
    Type 'MouseEvent' is missing the following properties from type 'MouseEvent<HTMLElement, MouseEvent>': nativeEvent, isDefaultPrevented, isPropagationStopped, persist   Overload 2 of 2, '(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions | undefined): void', gave the following error.     
Argument of type '(event: MouseEvent<HTMLElement, MouseEvent>) => void' is not assignable to parameter of type 'EventListenerOrEventListenerObject'.       
Type '(event: MouseEvent<HTMLElement, MouseEvent>) => void' is not assignable to type 'EventListener'.         Types of parameters 'event' and 'evt' are incompatible.           Type 'Event' is missing the following properties from type 'MouseEvent<HTMLElement, MouseEvent>': altKey, button, buttons, clientX, and 18 more.

My code works as expected in all the 3 types I have tried. The only way for me to hide the TS errors is using any, but that is not the best practice. Thanks in advance!


Solution

  • If you hover over addEventListener, TypeScript tells you that the correct definition for a click handler for the window object would be (this: Window, ev: MouseEvent) => any.

    Beyond that, for hideKeyboard, you would need an event.target instanceof Node check, as contains() expect a Node element and event.target is of type EventTarget | null.

    function hideKeyboard(this: Window, event: MouseEvent):any {
      if (!showKeyboard) return; 
      if (
        keyboard.current &&
        event.target instanceof Node &&
        !keyboard.current.contains(event.target)
      ) {
        setShowKeyboard(false);
      }
    }