react-select: [V2] [Typescript] ValueType
The problem
I’m using React-Select v2 in typescript and the onChange event handler has a first parameter with a the type ValueType<T> which is declared like this in the typings:
export type ValueType<OptionType> = OptionType | OptionsType<OptionType> | null | undefined;
The problem is that, because of that I can’t declare my event handler as:
private handleChange = (selected: MyOption) => {
/** Code **/
}
Instead I have to declare it like this:
private handleChange = (selected?: MyOption | MyOption[] | null) => {
/** Code **/
}
I can also declare ValueType in my project, but that’s a bit too much.
Possible solution:
Maybe there are better ways but one of the solutions, which would still be a bit awkward, would be to export the ValueType type so we can at least use that and not declare the value type in that manner.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 27
- Comments: 23 (4 by maintainers)
I had the same issue when using a select that allows single-select only. I was getting the same error as @bradchristensen.
I solved this by defining a type and doing a type assertion before using the selected option.
Hit this issue too and found this thread in my search for answers. The suggestions above didn’t resolve the issue for me, but I think helped point me in the right direction!
I was super confused about this for a while and then I realised it’s pretty easy to solve with a type check before you attempt to use the
selectedOption. If you explicitly check whetherselectedOptionis an array, TypeScript then understands that it is not possible for the value to be an array from that point forward and allows you to use it like you expect to.For reference, below is the error I was getting, which goes away when the
Array.isArraycheck is uncommented:The following worked for me using a multiselect:
Hopefully this will help others who stumble upon this issue.
using: “react-select”: “^3.0.4” “@types/react-select”: “^3.0.0”
The isArray check no longer works for me
I think the type definitions have gotten a little more complex in v3 and now typescript no longer can infer that
isArray === falseshould reject theOptionsTypetype.Not trying to be an ass; I generally agree with you. I would like to point out, however, that this whole thing can be obviated by being more specific about prop types and template types. The type that gets called with
onChangeis limited by the prop types, so there could be a template parameter specifying whether this is a multi-select, an optional select, etc. and then the type of value type could be specific instead of just “It could be an array, a value, null, or undefined”. In that case, you’d be able to assume that the parameter is what you expect it to be. So usingashere is kind of just bypassing the bypass.Also, yeah, not that I’m personally willing to take the time to do it, but I do think the ideal solution involves doing compile-time matching of prop types to deduce what the
ValueType<OptionType>actually is in contexts likeonChangewhere it should only ever be called with one value in a non-optional, non-multi select.Now that
Array.isArraydoesn’t seem to be working, I’m just using'length' in option:@tony
Here is the default type for
OptionType:From: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-select/src/Select.d.ts#L55
And here is the type for
ValueType<OptionType>:From: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-select/src/types.d.ts#L13
Unfortunately, the
react-selecttype definitions provided by DefinitelyTyped do not export the defaultOptionTypebecause I believe it can be overridden and defined by the user somehow. But in ourpdx-connectproject, we just used the defaultOptionTypeand copied the type into our own definitions:Now, the
onChangecallback is defined as producing aValueType<OptionType>as the first parameter:From: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-select/src/Select.d.ts#L168
But the
ValueType<OptionType>is a complex union type and I figured it would be easier to deal with if it was just aOptionType[]. So, I create a simpleOptionType.resolveutility function to perform this conversion wherever it was needed and to avoid duplicating code:This
resolvefunction simply performs “type differentiating” which you can read about here: https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-typesThis is essentially what has been suggested earlier in this thread: Use
Array.isArrayto check the type and handle it accordingly. I just packaged it up in a nice utility function that also checks fornullorundefinedand returns a single concrete type.Ideally, the author of the
react-selectshould add this utility function to his library so everyone can use it. I’m okay to release this snippet under MIT so others can use it and hopefully it gets put directly into thereact-selectlibrary.Also, note to everyone else in this thread: Avoid using
astype-casting in TypeScript. It bypasses the safety of the type system and it’s a good way to introduce runtime errors. Theaskeyword does not perform any runtime checks and code further along could break and it becomes a nightmare tracking down the bug.The solution from @plotka worked for me but it leaves the feeling that something is not right…
Is there a convenient solution without Array.isArray or as OptionType checking? Is it going to be implemented?
Option 1, intuitively expected, value as is
Doesn’t work because React-Select doesn’t know what value we are passing
Option 2 (by @ericmackrodt and @bradchristensen), with explicit types in handler signature
Doesn’t work because
IContact[]is not the same asReadonlyArray<IContact>Option 3 (by @plotka), using
ascoercionThough it works without errors, using
asis not the best solution because it eliminates the power of TS checksThis seems to work , in case someone is looking for something similar to it
tsserver’s hover shows that the extracted type signature is
Is there a reason the type cannot simply be
{ value: string; label: string; } | Array<{ value: string; label: string; }instead of{ value: string; label: string; } | OptionsType<{ value: string; label: string; }?