TypeScript compiler error with ref passed from getInputProps to component using a forwardRef
-
downshiftversion: 3.2.10 -
nodeversion: v11.15.0 -
npm(oryarn) version: 6.7.0 -
reactversion: 16.8.4 -
styled-componentsversion: 4.3.1
Relevant code or config
import * as React from "react";
import { render } from "react-dom";
import styled from "styled-components";
import Downshift from "downshift";
const Input = styled.input`
width: 200px;
`;
function App() {
return (
<Downshift>
{({ getInputProps }) => (
<div>
<Input
{...getInputProps({
onKeyUp(e: React.KeyboardEvent<HTMLInputElement>) {
// handle key up
}
})}
/>
</div>
)}
</Downshift>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
What you did: Attempted to pass a custom getInputProps to a component which implements a forward ref (in this case, to an <input> element).
What happened: A compiler error from TypeScript:
Type '{ onKeyUp: ((e: KeyboardEvent<HTMLInputElement>) => void) & ((event: KeyboardEvent<HTMLInputElement>) => void); disabled?: boolean; accept?: string; acceptCharset?: string; action?: string; ... 354 more ...; key?: Key; }' is not assignable to type 'Pick<Pick<Pick<DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "form" | "style" | "title" | "pattern" | "onChange" | "onSelect" | "children" | ... 277 more ... | "onTransitionEndCapture"> & { ...; }, "form" | ... 284 more ... | "onTransitionEndCapture"> & Partial<...>, "form" | ... 284 mo...'.
Types of property 'ref' are incompatible.
Type 'LegacyRef<HTMLInputElement>' is not assignable to type '((instance: HTMLInputElement) => void) | RefObject<HTMLInputElement>'.
Type 'string' is not assignable to type '((instance: HTMLInputElement) => void) | RefObject<HTMLInputElement>'.ts(2322)
Reproduction repository: https://codesandbox.io/s/loving-diffie-njech
Problem description: Type of ref is incompatible, even though the component appears to behave correctly with the forwarded ref.
Suggested solution: Change the type definitions to (optionally?) remove the LegacyRef part.
I looked into the codesandbox but I don't see any error with ref. Instead I see one with the <Input> you are using, that it does no accept onKeyUp (or onKeyDown for instance). If you cast it to any const Input: any then it works for me.
Try to fix it at your end or change the codesandbox with the relevant error. Thank you!
@silviuavram the error is there, it says Types of property 'ref' are incompatible. (the second line of the error message in my original post). I included one custom prop onKeyUp to make the use case more realistic but you can remove it and pass an empty object {} to see the same error, slightly less hidden.
Typing the return value of the function as any is indeed what I have done in my application, but I don't think that disabling type checking is the best solution!
I'm also getting this error and the 'error with ref' message is clear in the codesandbox repro that @fenech created.
I ran into this as well, it seems to be caused by components that are forwarding the ref. I'm assuming styled does this. I was able to replicate it just creating a super simple component that wraps a std <input> and uses React.forwardRef vs just using the base input element does not cause any errors.
Looking at the react types for 16.9 there are two ref types:
type Ref<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"] | RefObject<T> | null;
type LegacyRef<T> = string | Ref<T>;
and the typing for the InputProps loads the React.HTMLProps which uses LegacyRef
export interface GetInputPropsOptions
extends React.HTMLProps<HTMLInputElement> {
disabled?: boolean
}
It looks like what happening is that the typing for React.forwardRef does not support the LegacyRef (aka string), it's just using the regular ref:
interface RefForwardingComponent<T, P = {}> {
(props: PropsWithChildren<P>, ref: Ref<T>): ReactElement | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
All that being said, I'm not sure how to fix it.
Edit, overriding the types as a temporary fix works:
interface GetInputPropsOptionsRef extends GetInputPropsOptions {
ref?: Ref<HTMLInputElement>
}
<Input {...getInputProps({}) as GetInputPropsOptionsRef} />
Can someone create a PR if there is a fix? Please also look at the useSelect and useCombobox typings maybe they need to be fixed as well. Thanks!
Similarly, to get around this issue I've been creating new types that look like this:
export type IntrinsicDiv = JSX.IntrinsicElements['div'] & { ref?: Ref<HTMLDivElement> };
I fix up the ref type like this, but it feels wrong:
type ConvertRef<T, Target extends HTMLElement> = Omit<T, 'ref'> & { ref?: Ref<Target> }
getInputProps doesn't even return a ref unless given one so should it even give an opinion on what that ref should look like?
I'm trying the following:
<Downshift>
{({ getInputProps }) => (
<div>
<TextField
variant="filled"
{...getInputProps({
})}
/>
</div>
) as GetInputPropsOptionsRef}
</Downshift>
after declaring:
interface GetInputPropsOptionsRef extends GetInputPropsOptions {
ref?: Ref<HTMLInputElement>
}
but I still get the same error:
Type '{ disabled?: boolean | undefined; accept?: string | undefined; acceptCharset?: string | undefined; action?: string | undefined; allowFullScreen?: boolean | undefined; allowTransparency?: boolean | undefined; ... 357 more ...; variant: "outlined"; }' is not assignable to type 'IntrinsicAttributes & TextFieldProps'. Type '{ disabled?: boolean | undefined; accept?: string | undefined; acceptCharset?: string | undefined; action?: string | undefined; allowFullScreen?: boolean | undefined; allowTransparency?: boolean | undefined; ... 357 more ...; variant: "outlined"; }' is not assignable to type 'OutlinedTextFieldProps'. Types of property 'onChange' are incompatible. Type 'FormEventHandler<HTMLInputElement> | undefined' is not assignable to type 'ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> | undefined'. Type 'FormEventHandler<HTMLInputElement>' is not assignable to type 'ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>'. Types of parameters 'event' and 'event' are incompatible. Type 'ChangeEvent<HTMLInputElement | HTMLTextAreaElement>' is not assignable to type 'FormEvent<HTMLInputElement>'. Types of property 'currentTarget' are incompatible. Type 'EventTarget & (HTMLInputElement | HTMLTextAreaElement)' is not assignable to type 'EventTarget & HTMLInputElement'. Type 'EventTarget & HTMLTextAreaElement' is not assignable to type 'EventTarget & HTMLInputElement'. Type 'EventTarget & HTMLTextAreaElement' is missing the following properties from type 'HTMLInputElement': accept, align, alt, capture, and 26 more.ts(2322) (alias) function TextField(props: TextFieldProps): JSX.Element
Does someone have any idea how to fix it? I'm using React 17.0.2 and downshift 6.1.7 and material-ui 5.0.0