Home > Enterprise >  How to use a property in type to index type of a property in an interface
How to use a property in type to index type of a property in an interface

Time:03-28

I have no idea if this is possible or even advisable. I'm trying to use a property in a type, to index an interface and retrieve the type of that given property in the interface. It's a little hard to describe, so here's what I'm trying to do:

type Props = {
  propertyName: keyof AppState;
  options: AppState[propertyName];
};

Dummy interface:

export interface AppState {
  A: string;
  B: number[];
}

However, it's not working. I assume it doesn't like the self reference, I tried to use this.propertyName as well.

Edit:

The answer by szaman was very helpful and did exactly what I asked. I just didn't realize it wouldn't immediately work as I thought as a function argument property type. I asked an incomplete question out of ignorance, my mistake.

Here's the Props type currently:

type Props<T extends keyof AppState> = {
  propertyName: T;
  // options is going to be an array of type of given propertyName values
  options: AppState[T][];
}

The AppState dummy interface is kept the same. Here's a dummy functional component:

const MyComponent: React.FC<Props> = props => {...}

The problem now is that I need to specify what exact property of AppState the functional component should be typed as during function declaration. The error is as follows:

Generic type 'Props' requires 1 type argument(s).

My goal is to be able to use this function elsewhere with any property that AppState has. TS would automatically detect the type of the property value, and warn me if the options type mismatches what the given property value's type is.

Working case:

<MyComponent propertyName="A" options={["foo", "bar"]} />

Error case:

<MyComponent propertyName="A" options={[0, 1]} />

In the second error case, my intention is for TS to auto detect that the supplied options array values are not equal to the type of propertyName "A" of AppState. Is this possible to do?

CodePudding user response:

You can achieve that using generic types with constraints.

type Props<T extends keyof AppState> = {
  propertyName: T;
  options: AppState[T];
};


let a: Props<"A"> = {
  propertyName: "A",
  options: "foo"
}

// this is invalid:
let aInvalidOptions: Props<"A"> = {
  propertyName: "A",
  options: [8, 9]
}

Here we're using Props<T>, which is a special syntax for generic types. We're also adding a constraint on it (type Props<T extends keyof AppState) so that it only accepts types based on our AppState interface.

Then we are using the passed type to determine the type of the options property. As a bonus, we can even make sure that the propertyName matches our constrained type.

Playground

CodePudding user response:

Your current type is currently having propertyName and options which are unions of the possible keys and values, respectively. However, you need to create a top-level union of each of the properties and their respective options. This can be done via mapped types which allow us to iterate through the keys to create a new object. Then, we can flatten this new created object by accessing it with keyof T to get a union of the possible combinations of the { propertyName, options } object.

type Props<T> = {
  [K in keyof T]: { propertyName: K; options: T[K][] }
}[keyof T];

//   { propertyName: "hello"; options: string[]; }
// | { propertyName: "to"; options: number[]; }
// | { propertyName: "you"; options: string[]; }
type MyProps = Props<{
  hello: string;
  to: number;
  you: string;
}>;

const foo: MyProps = {
  propertyName: "hello",
  options: ["asdf"]
};

const foo2: MyProps = {
  propertyName: "to",
  options: [5, 3],
};

const foo3: MyProps = {
  propertyName: "hello",
  // @ts-expect-error It fails if you have an invalid option.
  options: ["asdf", 2]
};

const foo4: MyProps = {
  // @ts-expect-error It fails if you have an invalid name.
  propertyName: "not-a-valid-name",
  options: ["hey", 'you']
};

TypeScript Playground Link

  • Related