wwohlers
wwohlers

Reputation: 379

"any" constrained as keyof not working Typescript

Here are the definitions I currently have:

interface Action<T extends string, P> {
  type: T;
  payload: P;
}

type ActionDefinitions = {
  setNumber: number;
  setString: string;
}

type ActionCreator<A extends keyof ActionDefinitions> =
  () => Action<A, ActionDefinitions[A]>;

My thinking is that when I make an ActionCreator<any>, any must be constrained to a key of ActionDefinitions. This is important because when I define a type like this:

type Creators = {
  [K: string]: ActionCreator<keyof ActionDefinitions>
};

I need automatic type inference for each value in this object. So for example, if I were to define a Creators object like this:

const testCreators: Creators = {
  foo: () => ({
    type: "setNumber",
    payload: "string",
  })
}

This should cause an error because foo should be inferred to be an ActionCreator<"setNumber">, which should be of type Action<"setNumber", number>, and therefore the payload should be a number, not a string.

However, TypeScript simply allows payload to be of any type, which seems wrong because if the generic type A is equal to "setNumber", then foo should have to return Action<"setNumber", ActionDefinitions["setNumber"]>, i.e., Action<"setNumber", number>...but it allows any instead of number.

Anyone know why this is or how I can fix it?

Upvotes: 1

Views: 64

Answers (1)

aleksxor
aleksxor

Reputation: 8370

The problem is keyof ActionDefinitions is a union type and passing it into ActionCreator you eventually get type:

type ActionCreator = () => Action<keyof ActionDefinitions, string | number>

That allows string | number as a payload.

What you really need is to iterate over keys of keyof ActionDefinitions and pass concrete keys into ActionCreator:

type Creators = {
  [K: string]: { [I in keyof ActionDefinitions]: ActionCreator<I> }[keyof ActionDefinitions]
};

playground link


updated with a bit more generic version of ActionCreator type:

type ActionCreator<T, K extends Extract<keyof T, string> = Extract<keyof T, string>> = {
  [I in K]: () => Action<I, T[I]>;
}[K]

type Creators = {
  [K: string]: ActionCreator<ActionDefinitions>
};

playground link

Upvotes: 1

Related Questions