0

I have the below definition

interface IAutoCompleteInputProps<T> {
    textMember: keyof T;
    imageMember: keyof T;
    data: T[];
}

But I also want to ensure the value of T[textMember] must be string, too. How can I achieve that?

=== Update: After I implemented the @R Pasha's answer, when I try to access the textMember by a.data[a.textMember] (I expect it will be a string) but the IDE says that

(property) value: T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]
Type 'T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]' is not assignable to type 'string'.
  Type 'T[T[keyof T] extends string ? keyof T : never]' is not assignable to type 'string'.
    Type 'T[keyof T]' is not assignable to type 'string'.
      Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'string'.
        Type 'T[string]' is not assignable to type 'string'.ts(2322)

I can cast it to string, but just want to know why? Thanks

PS the code that I got error

function Application<T>(props: IAutoCompleteInputProps<T>) {
  const x = props.data[0][props.textMember]; // x here is not string
}

Here is Playground Link

Han
  • 3,272
  • 3
  • 24
  • 39

2 Answers2

2

you can use Conditional types

type StringProperties<T> = Pick<T, { 
    [K in keyof T]: T[K] extends string ? K : never 
}[keyof T]>;

interface IAutoCompleteInputProps<T>{
    textMember: keyof StringProperties<T>;
    imageMember: keyof T;
    data: T[];
}

interface Type1 {
  field1: number; // <- is number
  field2: string;
}

let x : IAutoCompleteInputProps<Type1> = {
  textMember : 'field1', // <- field1 must be string
  imageMember: 'field2',
  data: []

}
R Pasha
  • 700
  • 5
  • 21
  • Great! This works when I input value for the props but when I'm trying to extract the value of text member there is still an error in the Visual studio code. I updated question. Do you know why? – Han Jul 11 '20 at 11:26
  • hmm, interesting. can you make a stackblitz.com ? – R Pasha Jul 12 '20 at 02:42
  • yes, I created a playground link, updated in post, because link is too long – Han Jul 12 '20 at 02:49
  • it seems Typescript doesn't support this scenario, more discussion at https://stackoverflow.com/questions/55641731/typescript-conditional-type-complains-type-not-assignable – R Pasha Jul 12 '20 at 03:35
0

Pick properties of string value using this helper:

type PropertyOfValue<T, V> = {
  [K in keyof T]-?: T[K] extends V
    ? K
    : never
}[keyof T];

Usage:

interface Type1 {
  field1: number; 
  field2: string;
}

const props: IAutoCompleteInputProps<Type1> = {
  data: [
    { field1: 42, field2: 'foo' }
  ],
  imageMember: 'field1',
  textMember: 'field2'
}

props.textMember; // "field2"
props.data[0][props.textMember]; // string

TypeScript Playground

Karol Majewski
  • 23,596
  • 8
  • 44
  • 53
  • Thanks @Karol Majewski, your answer is somewhat similar to the first one. When I use it in another Generic function, it still cannot be deducted to `string`. Any idea? I updated my question – Han Jul 11 '20 at 15:55