1

Example

const food = {
  fruit: ['apples', 'oranges'],
  meat: ['chicken', 'pig']
}

function makeFood(ingredient, category) {
 switch(true) {
   case category === 'fruit' && ingredient === 'apples' {
    // do something
   }
   case category === 'fruit' && ingredient === 'oranges' {
    // do something
   }
   case category === 'meat' && ingredient === 'chicken' {
    // do something
   }
   case category === 'meat' && ingredient === 'pig' {
    // do something
   }
 }
}

what's the best way to type category is a key of food and ingredient is a value?

Currently I'm doing

function(ingredient, keyof typeof category) {

would love to keep the relation between the two. So TS will know the ingredient type based in the category value.

Norfeldt
  • 8,272
  • 23
  • 96
  • 152

1 Answers1

2

You use typescript Generics. In this you wanted category to be generic parameter, which ingredient can then use.

You'll need to use as const (see here) on your source object to make sure it's specific string are part of its type.

const food = {
  fruit: ['apples', 'oranges'],
  meat: ['chicken', 'pig']
} as const // important to add "as const" here

function makeFood<
  K extends keyof typeof food // Generic parameter
>(
  ingredient: typeof food[K][number], // use K to lookup types for ingredient
  category: K // provide the key to to use here as K
) {
 switch(true) {
   case category === 'fruit' && ingredient === 'apples': {
    // do something
   }
   case category === 'fruit' && ingredient === 'oranges': {
    // do something
   }
   case category === 'meat' && ingredient === 'chicken': {
    // do something
   }
   case category === 'meat' && ingredient === 'pig': {
    // do something
   }
 }
}

makeFood('apples', 'fruit')
makeFood('pig', 'fruit') // type error

Playground

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • Also cool to note that it will prevent you from using typos in the switch. – Ruan Mendes Nov 24 '21 at 18:18
  • Thank you very much for such a quick answer – Norfeldt Nov 24 '21 at 18:25
  • 1
    An alternative without generics is `function makeFood(ingredient: typeof food[typeof category][number], category: keyof typeof food)` – Ruan Mendes Nov 24 '21 at 18:29
  • And I keep thinking there must be a way to use `infer` so the compiler can infer that in the first `case` clause, ingredient could only be `'apples'|'oranges'` – Ruan Mendes Nov 24 '21 at 18:32
  • @JuanMendes your suggestion without generics is event better since it keeps the relation between the types when autocompleting in vscode. If you post it as an answer I'll give it a vote. – Norfeldt Nov 25 '21 at 07:32
  • @Norfeldt Can you explain what worked better? I couldn't find a difference. Since you have this understanding, it may be better for you to post that answer yourself, then I can learn from it. – Ruan Mendes Nov 29 '21 at 17:17
  • I see now that it works the same in Playground - it just seemed to me that it was working differently in vscode, but I doubt that. Anyway I like your suggestion very much and thank you for that – Norfeldt Nov 30 '21 at 14:44