Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript + useRef<X|Y>: Type X is not assignable to type Y

This simple component:

const Editable = ({multiline}: { multiline: boolean }) => {
    const ref = useRef<HTMLInputElement|HTMLTextAreaElement>(null);
    return <div>
        {multiline ? <textarea ref={ref}/> : <input ref={ref}/>}
    </div>
}

Have the following TypeScript errors:

Error:(7, 32) TS2322: Type 'RefObject<HTMLInputElement | HTMLTextAreaElement>' is not assignable to type 'string | ((instance: HTMLTextAreaElement | null) => void) | RefObject<HTMLTextAreaElement> | null | undefined'.
  Type 'RefObject<HTMLInputElement | HTMLTextAreaElement>' is not assignable to type 'RefObject<HTMLTextAreaElement>'.
    Type 'HTMLInputElement | HTMLTextAreaElement' is not assignable to type 'HTMLTextAreaElement'.
      Type 'HTMLInputElement' is missing the following properties from type 'HTMLTextAreaElement': cols, rows, textLength, wrap
Error:(7, 53) TS2322: Type 'RefObject<HTMLInputElement | HTMLTextAreaElement>' is not assignable to type 'string | ((instance: HTMLInputElement | null) => void) | RefObject<HTMLInputElement> | null | undefined'.
  Type 'RefObject<HTMLInputElement | HTMLTextAreaElement>' is not assignable to type 'RefObject<HTMLInputElement>'.
    Type 'HTMLInputElement | HTMLTextAreaElement' is not assignable to type 'HTMLInputElement'.
      Type 'HTMLTextAreaElement' is missing the following properties from type 'HTMLInputElement': accept, align, alt, checked, and 23 more.

The errors could be ignored using this following line:

const ref = useRef<any>(null);

How can useRef be used with the correct type and without errors?

like image 365
Udi Avatar asked Nov 22 '20 06:11

Udi


1 Answers

Solution 1: Type assertion

const ref = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
return (
    <div>
        {multiline ? <textarea ref={ref as React.RefObject<HTMLTextAreaElement>}/>:
            <input ref={ref as React.RefObject<HTMLInputElement>} />}
    </div>
)

<textarea ... /> expects a ref that takes HTMLTextAreaElement. HTMLTextAreaElement contains different properties than HTMLInputElement, so the supertype HTMLTextAreaElement | HTMLInputElement cannot be assigned to one of the nodes. A type assertion is perfectly fine here. Pros: we are forced to narrow ref in a type-safe way. Cons: Type assertions are a bit verbose.

Solution 2: Intersection type for ref

const ref = useRef<HTMLInputElement & HTMLTextAreaElement>(null);
return (
    <div>
        {multiline ? <textarea ref={ref } /> :
            <input ref={ref} />}
    </div>
)

This works, as HTMLInputElement and HTMLTextAreaElement don't have conflicting property types (otherwise it would result in never). Pros: more compact code. Cons: Make sure to narrow the element before. E.g. you might be able to invoke following code for HTMLInputElement leading to runtime errors:

ref.current && ref.current.cols // input does not have cols

Playground

like image 109
ford04 Avatar answered Nov 13 '22 09:11

ford04