react-phone-number-input: `react-hook-form` component typings seem to be incorrect
The default import from 'react-phone-number-input/react-hook-form'
has type any
.
I assume this is because the type
type PhoneInputWithCountrySelectType<InputComponentProps = DefaultInputComponentProps, FormValues> = React.ComponentClass<Props<InputComponentProps, FormValues>, State<Props<InputComponentProps, FormValues>>>
is incorrect.
First of all, the type parameter FormValues
follows an optional type parameter, which is not allowed in TypeScript.
In addition to that, FormValues
is a required parameter, but the component type uses PhoneInputWithCountrySelectType
without any parameters.
declare const PhoneInputWithCountrySelect: PhoneInputWithCountrySelectType;
As a result, it seems that TS cannot parse that and assigns any
as the type.
As a workaround, I have swapped the parameters and assigned {}
to FormValues
, as I have no idea what it’s supposed to be, and it works.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 27 (17 by maintainers)
@catamphetamine Actually, I just found out that there’s a new TS feature that allows you to fix a generic function’s type. So you can just write
typeof PhoneInput<CustomProps, CustomFormValues>
now.In that case, we don’t need the export. Just move the generic to the right and it should all work great.
@catamphetamine It can be, it’s just that giving a type for the user’s wrapper component is not something you need to do. In my code base, most components are typed as
FC<Props, Something>
. Obviously I wouldn’t want a library trying to make me use something else. Especially since in my caseFC
is a custom type as well, so you can’t use it like that.So, I don’t need a
PhoneInputComponentType
for my wrapper component, I just need aPhoneInputType
to represent your component in case I need it.Because it’s a type for the user component that wraps your component, so you don’t really know
Props
— those are the user component’s props. AndCustomInputComponent
is, of course, a custom component, so you don’t know it either. That’s why you have to expose a generic type (PhoneInputType
in this case).In any case, it’s totally fine for the user to define their own component’s type. What you can do is to make it easy for them to get your component’s type, hence the need for the second type.
Because it has opposite variance here.
When generating a
PhoneInputWithCountrySelect
, we can assume the user doesn’t mind aPhoneInputWithCountrySelect<DefaultInputComponentProps>
.When accepting a
PhoneInputWithCountrySelect
’s type as an argument (to get the type of its props), we can’t assume allPhoneInputWithCountrySelect
s arePhoneInputWithCountrySelect<DefaultInputComponentProps>
. They might have custom input props, hence the inferred type isPhoneInputWithCountrySelect<unknown>
, and the resulting props give you stuff likeControl<unknown>
, which isn’t accepted by the actualPhoneInputWithCountrySelect
component.The workaround is essentially something like this:
The main problem here is that you have to describe the type of the
PhoneInput
again, saying, yeah, it’s a(props: PhoneNumberInputProps<PropsOf<CustomInputComponent>>) => JSX.Element>
, which is a handful.A solution would be to have a second type which is exactly as it is in the current version:
Now this can be used to construct the props type, like so:
Which is as good as it gets.
So basically you need both types, one for the actual component’s type, that’s the one I proposed earlier, and the other to represent the component’s type with fixed generic parameters, that’s the one that is there right now. The second one needs to be exported as well.
You can.
I’ve played around with this a little, and while the generics work, they are also less convenient in some cases. If you have a wrapping component that adds
PhoneInput
’s props to its own, right now you can just usetypeof PhoneInput
to extract the types automatically. With generics, the parameters evaluate to<unknown, any>
in this case, which doesn’t accept anything. As a workaround, any wrapper has to also be generic and construct the props type manually.I’m not sure what the use would be for generics anyway. Normally generic components are useful because TS infers the type from one prop and uses it to validate others, but here each parameter is only used once. So it’s probably not worth it in this case.
Thanks for the quick fix, eveything works now.
If I go into TypeScript expert mode, looking at the types, I can also say that this makes
FormValues
useless. The API is essentially the same as before that commit I mentioned.FormValues
is only used forControl
, which already provides a default ofRecord<string, any>
, as you noted. So you are now drilling through the type hierarchy only to replace the default type with the exact same type.The only difference is that you can in theory import the generic type manually and provide a different value. But the exported component’s type is non-generic and can’t be changed by the developer.
You can safely leave this as it is, I just already typed that when you released the fix, haha.