React-select: Generic type 'ControlProps' requires 2 type argument(s). TS2314

Created on 2 Dec 2020  路  13Comments  路  Source: JedWatson/react-select

Are you reporting a bug or runtime error?

Yes, but in TypeScript.

Description

I can't figure out what in the react-select changelog caused this but upgrading to [email protected] has broken my Chromatic deployments so I'm assuming the type of something changed.

Here's the error:
Generic type 'ControlProps' requires 2 type argument(s). TS2314

And the code that caused it:

export const controlStyles = (base: CSSProperties, state: ControlProps<{}>) => ({
  ...base,
  height: "3.2rem"
})

This error is actually originating from a lot of types I've used from @types/react-select including NoticeProps, OptionProps, ValueType, etc. I'm assuming it's because of the upgrade because with [email protected], everything worked smoothly.

Feel free to look at the logs of the errors for more details or the source.

Most helpful comment

@cseas

What is IsMulti

The IsMulti generic makes it so that we can correctly narrow ValueType in various scenarios. Previously ValueType looked like this:

type ValueType<OptionType extends OptionTypeBase> = OptionType | OptionType[] | null | undefined;

Now it looks like this:

type ValueType<OptionType extends OptionTypeBase, IsMulti extends boolean> =  (IsMulti extends true ? OptionType[] : OptionType) | null | undefined;

What does this mean? It means that if we're dealing with a single-select component (IsMulti is false) then we know that ValueType will not be an array. But if we know that we're dealing with a multi-select component (IsMulti is true) then we know that ValueType will be an array.

A practical example (see this CodeSandbox for the full code) where this makes a difference is the callback function passed into the onChange prop which provides an argument of type ValueType<OptionType> as it's first parameter. Previously code like this would result in a type error:

export default class App extends React.Component {
  onChange = (newValue: ValueType<OptionType>) => {
    if (newValue != null) {
      // TS error: "Property 'label' does not exist on type 'OptionType | OptionType[]'."
      // To fix this we would have to cast `newValue` to `OptionType`.
      console.log(newValue.label);
    }
  };

  render() {
    return <Select options={colourOptions} onChange={this.onChange} />;
  }
}

However we know that it's not an array because this isn't a multi-select component and we shouldn't have to cast. Therefore we can now set the second generic parameter to false to specify that this is not a multi-select component and now it compiles correctly:

  onChange = (newValue: ValueType<OptionType, false>) => {

why is it not a part of Props from react-select

It is part of the Props type, it's just that it defaults to false (and is therefore optional). If you don't provide it, and you don't set the isMulti prop to true, it will set it to false and assume you're dealing with a single-select component.

All 13 comments

These errors originate from a recent change in @types/react-select. Most of these types now take a second generic parameter (IsMulti) that specifies whether isMulti is set to true. In most cases, you should be safe setting that to boolean if you want to allow isMulti to be either true or false.

Let me know if you have any more questions about how to deal with the new IsMulti generic parameter. Also, if you have an issue with the TypeScript types in the future, the DefinitelyTyped repo is probably a better place to create the issue. Just make sure to tag me and I'll get around to taking a look at it.

Running into the same issue as well but with ValueType, been using this package for over a year on a client's project and it broke all of our pipelines. In the future you guys should make type changes like that a major version upgrade not a minor so it doesn't break builds.

@prescottbreeden This is due to a change in @types/react-select hosted on DefinitelyTyped (not this repository). @types package versions do not follow SemVer so you should pin your package versions to an exact package version to avoid these sorts of problems in the future. Unfortunately breaking changes in minor/patch versions of @types packages are inherent to @types and there's no way around this problem (that I know of).

If you have any issues with the new types, let us know. We're only looking to improve the types, so feedback would be appreciated if that's not the case.

I see, that makes sense, thanks for the extra info

Okay, so we put boolean as a second argument. And what do we do with the error in isMulti?

<MySelect
  isMulti
/>

now gives the following error:
Type 'true' is not assignable to type 'false | undefined'.ts(2322) Select.d.ts(141, 3): The expected type comes from property 'isMulti' which is declared here on type 'IntrinsicAttributes & SearchProps'

If you have a custom Select component (in this case MySelect), you'll likely have to add IsMulti as a second generic parameter (i.e., IsMulti extends boolean) to the type of MySelect and pass IsMulti along to the Select component within MySelect.

If you need more specific help than that, you'd have to show me the code for MySelect.

Yeah, the source is here.

The type of custom Select component extends { Props } from "react-select"

I'm a bit confused. What is IsMulti and why is it not a part of Props from react-select? Sorry, I'm not really good with generics.

I took the low road and rolled back my types and react-select to 3.0.8

@cseas The easiest way to fix your case is to change

export interface SearchProps extends Props {

to

export interface SearchProps extends Props<OptionTypeBase, boolean> {

(you'll have to import OptionTypeBase from react-select).

Although I will warn you that your types aren't actually safe, since you're not propagating the generics. So there are potential bugs that the types should be catching for you, but won't be able to because you're essentially throwing away the generics.

@prescottbreeden Just so you know, you shouldn't have to roll react-select itself back. Just pinning @types/react-select to @types/[email protected] should be sufficient.

@cseas

What is IsMulti

The IsMulti generic makes it so that we can correctly narrow ValueType in various scenarios. Previously ValueType looked like this:

type ValueType<OptionType extends OptionTypeBase> = OptionType | OptionType[] | null | undefined;

Now it looks like this:

type ValueType<OptionType extends OptionTypeBase, IsMulti extends boolean> =  (IsMulti extends true ? OptionType[] : OptionType) | null | undefined;

What does this mean? It means that if we're dealing with a single-select component (IsMulti is false) then we know that ValueType will not be an array. But if we know that we're dealing with a multi-select component (IsMulti is true) then we know that ValueType will be an array.

A practical example (see this CodeSandbox for the full code) where this makes a difference is the callback function passed into the onChange prop which provides an argument of type ValueType<OptionType> as it's first parameter. Previously code like this would result in a type error:

export default class App extends React.Component {
  onChange = (newValue: ValueType<OptionType>) => {
    if (newValue != null) {
      // TS error: "Property 'label' does not exist on type 'OptionType | OptionType[]'."
      // To fix this we would have to cast `newValue` to `OptionType`.
      console.log(newValue.label);
    }
  };

  render() {
    return <Select options={colourOptions} onChange={this.onChange} />;
  }
}

However we know that it's not an array because this isn't a multi-select component and we shouldn't have to cast. Therefore we can now set the second generic parameter to false to specify that this is not a multi-select component and now it compiles correctly:

  onChange = (newValue: ValueType<OptionType, false>) => {

why is it not a part of Props from react-select

It is part of the Props type, it's just that it defaults to false (and is therefore optional). If you don't provide it, and you don't set the isMulti prop to true, it will set it to false and assume you're dealing with a single-select component.

Thanks a lot for the help and explanation!
I was able to make it work.
For anyone looking for reference, here's how I fixed the breaking types.

And yeah, I know the types could use some improvements. But that's mostly just because I'm not familiar with generics and just wanted to make react-select "work" in TypeScript with some sort of type checking.

It would be really great if there was some sort of implementation guide or reference available for how to properly use react-select with TypeScript. But unfortunately, the best I could find was scattered information across GitHub issues and StackOverflow answers.

I kinda started this repository to serve as an implementation reference for react-select with TypeScript and that eventually became this component.

@cseas Looks like you the use case I provided above is actually pretty common in your code base. I made a PR to fix the types in your project so that you don't have to cast in onChange anymore.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mjuopperi picture mjuopperi  路  3Comments

juliensnz picture juliensnz  路  3Comments

x-yuri picture x-yuri  路  3Comments

steida picture steida  路  3Comments

MindRave picture MindRave  路  3Comments