Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript compiler error with ref passed from getInputProps to component using a forwardRef #718

Open
fenech opened this issue Jun 18, 2019 · 9 comments

Comments

@fenech
Copy link

fenech commented Jun 18, 2019

  • downshift version: 3.2.10
  • node version: v11.15.0
  • npm (or yarn) version: 6.7.0
  • react version: 16.8.4
  • styled-components version: 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.

@silviuaavram
Copy link
Collaborator

I looked into the codesandbox but I don't see any error with ref. Instead I see one with the 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!

@fenech
Copy link
Author

fenech commented Jun 20, 2019

@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!

@stevejay
Copy link

I'm also getting this error and the 'error with ref' message is clear in the codesandbox repro that @fenech created.

@kirkchris
Copy link

kirkchris commented Oct 11, 2019

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}  />

@silviuaavram
Copy link
Collaborator

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!

@TakenPilot
Copy link

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> };

@fabb
Copy link
Contributor

fabb commented Jul 30, 2020

I fix up the ref type like this, but it feels wrong:

type ConvertRef<T, Target extends HTMLElement> = Omit<T, 'ref'> & { ref?: Ref<Target> }

@MarkFalconbridge
Copy link

getInputProps doesn't even return a ref unless given one so should it even give an opinion on what that ref should look like?

@alvaradojl
Copy link

alvaradojl commented Oct 25, 2021

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 | undefined' is not assignable to type 'ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> | undefined'.
Type 'FormEventHandler' 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'.
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants