0

I've been trying to wrap a component SelectSearchResult around AsyncSelect from react-select. I'd like that the props of my custom component to be almost the same of AsyncSelect with some exceptions.

import AsyncSelect, { Props as AsyncSelectProps } from "react-select/async";

// works, but Props allows all properties
type Props = AsyncSelectProps<{ label: string; value: string }>;

const SelectSearchResult = (props: Props) => {
    return <AsyncSelect {...props} />;
};

I assumed I just needed to omit the keys I didn't want.

type Props = Omit<AsyncSelectProps<{ label: string; value: string }>, "loadOptions">;

However, when I inspect Props, it has now the following format and I am clueless of why is it taking this shape from.

type Props = {
  [x: string]: any;
  [x: number]: any;
}
Rigotti
  • 2,766
  • 23
  • 28

2 Answers2

2

After a lot of searching I found out that the problem actually lies on the way AsyncProps is defined, because in its body an index signature is found which makes Exclude or Omit not work properly.

To illustrate the problem:

// This type accepts the known keys `a` and `b` as strings and anything 
// else with unknown keys
type MyType = {
    a: string;
    b: string;
    [key: string]: any; // index signature
};

type MyTypeWithoutA = Omit<MyType, "a">;

// I expected the object below to complain for the fact, that "b" is not defined
// but it doesn't happen
const obj: MyTypeWithoutA = {}; 

The solution I've found was to first get to create a derived type of MyType without the index signature and create an alternative version of Omit

type KnownKeys<T> = {
    [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;

// K is the union of keys I want to omit from the known keys of T
type OmitKnownKeys<T, K extends string> = Omit<Pick<T, KnownKeys<T>>, K>;

Rewriting the code to

type MyTypeWithoutA = OmitKnownKeys<MyType, "a">;

const obj: MyTypeWithoutA = {}; // error, b is not defined (what I wanted)

Important: this solution works for my specific case, but it actually drops the index signature from your type, making it less flexible, in other words you can't pass any arguments that'll match the index signature. Check this version if you want to keep your index signature.

Sources: 1, 2

Rigotti
  • 2,766
  • 23
  • 28
0

You can use never type and & operator. e.g.: type Props =AsyncSelectProps<{ label: string; value: string }> & {loadOptions: never };

When someone will try to call Props.loadOptions it will returns never and never is not assignable to any types, so it will raise error in next usage.

Przemyslaw Jan Beigert
  • 2,351
  • 3
  • 14
  • 19