schpet
schpet

Reputation: 10620

How to add types for a typescript function that takes in an array of generics

please see code sample below- i'd like some typesafety around the type of array entries that resetHistory takes in, but it's not creating a type error where i'd like it to! appreciate any tips.

typescript playground link

type Params = {
  profile: { userId: string };
  homepage: undefined;
  settings: { tab: "password" | "profile" };
};

// naive attempt at typing the history arg
function resetHistory<K extends keyof Params>(
  history: { name: K; params: Params[K] }[]
) {
  // ...
}

resetHistory([
  { name: "homepage", params: undefined },
  { name: "profile", params: { userId: "1234" } },
  { name: "settings", params: { userId: "xxx" } }, // <-- i want this to be a typeerror, and only accept { tab: "password" | "profile" }
]);

Upvotes: 1

Views: 39

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42198

What you need here is a union of all possible pairings. We can create that with a mapped type.

type HistoryEntry = {
  [K in keyof Params]: Params[K] extends undefined 
      ? { name: K; params?: undefined; }
      : { name: K; params: Params[K]; }
}[keyof Params]

For each key of the Params object, the acceptable value is an object with a name that matches that key and a params that matches the Params value for that key. That is { name: K; params: Params[K]; }.

I am adding a conditional check Params[K] extends undefined to allow you to omit the params entirely if they are undefined. So you can just pass { name: "homepage" }.

Our resetHistory function is no longer generic. It accepts an array where all entries are of type HistoryEntry.

function resetHistory( history: HistoryEntry[] ) {

This gives us all of the errors that we want:

resetHistory([
  { name: "homepage" }, // ok
  { name: "profile", params: { userId: "1234" } }, // ok
  { name: "settings" }, // error: params is missing
  { name: "settings", params: { userId: "xxx" } }, // error: tab is missing from params
  { name: "settings", params: { tab: "password" } } // ok
]);

Typescript Playground Link

Upvotes: 3

Related Questions