reactjstypescriptnext.jsreact-typescript

TypeScript throws Type 'KeyboardEvent | FocusEvent<HTMLInputElement, Element>' is not assignable to type 'FocusEvent<HTMLInputElement, Element>'.?


I have FormContextInput type and reusable Input component like the below:

export type FormContextInput  = {
  name:string,
  eventType: "onBlur" | "onKeyDown" | "onClick",
  validate: (event:React.FocusEvent<HTMLInputElement> | KeyboardEvent) => void | Promise<void>,
} & React.InputHTMLAttributes<HTMLInputElement>;

the Input component accepts a validate function. and I'm reusing it inside two different components A and B. the validation function of A triggers on onBlur, and the B's validation triggers on onKeyDown

Input code:

export default function Input({name, eventType, validate, ...props}:FormContextInput) {

  async function handleValidate(event:React.FocusEvent<HTMLInputElement> | KeyboardEvent) {
    try {
      await Promise.resolve(validate(event));
      setError("");
    } catch (error) {
      setError((error as Error).message);
    }

  }

  return (
      <input name={name} {...props}
      {...(eventType === "onBlur" && {onBlur:handleValidate})}
      {...(eventType === "onKeyDown" && {onKeyDown:handleValidate})}
      />
  )
}

A validation function :

function validation(event: React.FocusEvent<HTMLInputElement>) {}

B validation function:

function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {}

but I'm getting three errors on all components. A and B have an error line where I'm passing their validation function to the Input as a prop: A error:

Type '(event: FocusEvent<HTMLInputElement, Element>) => void' is not assignable to type '(event: FocusEvent<HTMLInputElement, Element> | KeyboardEvent) => void | Promise<void>'.
  Types of parameters 'event' and 'event' are incompatible.
    Type 'FocusEvent<HTMLInputElement, Element> | KeyboardEvent' is not assignable to type 'FocusEvent<HTMLInputElement, Element>'.
      Type 'KeyboardEvent' is missing the following properties from type 'FocusEvent<HTMLInputElement, Element>': relatedTarget, nativeEvent, isDefaultPrevented, isPropagationStopped, persist

B error:

Type '(event: KeyboardEvent<HTMLInputElement>) => void' is not assignable to type '(event: FocusEvent<HTMLInputElement, Element> | KeyboardEvent) => void | Promise<void>'.
  Types of parameters 'event' and 'event' are incompatible.
    Type 'FocusEvent<HTMLInputElement, Element> | KeyboardEvent' is not assignable to type 'KeyboardEvent<HTMLInputElement>'.
      Type 'FocusEvent<HTMLInputElement, Element>' is missing the following properties from type 'KeyboardEvent<HTMLInputElement>': altKey, charCode, ctrlKey, code, and 11 more

each one of them is complaining about the other event's type it seems to me like TypeScript ignores the | between the two types and doesn't recognise them as separate things!

Input error line is under the <input/> tag:

No overload matches this call.

I tried to change the type definition to React.SyntheticEvent but it also throws errors since it too loose and I'll have to narrow the event types. the only solution that "fixes" this is to use any but that has it's problems too.


Solution

  • The issue is that TypeScript expects the validate function to handle both FocusEvent and KeyboardEvent, but the validation functions in components A and B are specific to one type.

    Solution:

    Use a type guard in handleValidate to differentiate between FocusEvent and KeyboardEvent:

    async function handleValidate(event: React.FocusEvent<HTMLInputElement> | React.KeyboardEvent<HTMLInputElement>) {
      try {
        if ("relatedTarget" in event) {
          await validate(event as React.FocusEvent<HTMLInputElement>);
        } else {
          await validate(event as React.KeyboardEvent<HTMLInputElement>);
        }
        setError("");
      } catch (error) {
        setError((error as Error).message);
      }
    }
    

    This resolves the type conflict by narrowing the event type.