Stephen Koo
Stephen Koo

Reputation: 517

How do I make a generic typescript type function that has the return type inferred from the argument type?

I want to reuse this pattern for a number of argument object types, and for the return type of the function to be the same as the value of the object types

type IntentColorCategory = 'brand' | 'neutral' | 'semantic'
type IntentColor = 'primary' | 'secondary' | 'neutral' | 'error' | 'success'

// Understandably this doesn't work - intention is to have a function that
// has a return type corresponding to the value of the argument object
// values.
const getByIntentColorCategory<T extends any> = ({
  brand,
  neutral,
  semantic
}: Record<IntentColorCategory, T>) => (intent: IntentColor): T => {
  switch (intent) {
    case 'primary':
    case 'secondary':
      return brand;
    case 'neutral':
      return neutral;
    case 'error':
    case 'success':
    default:
      return semantic;
  }
}


// E.g.
getByIntentColorCategory<number>({ brand: 1, semantic: 2, neutral: 3 })
// I want to specify the object must have number values when used here and
//that the return type is a number

getByIntentColorCategory<string>({ brand: 'a', semantic: 'b', neutral: 'c' })
// I want to specify the object must have strings when used here and that
// the return type is a string

Upvotes: 0

Views: 75

Answers (2)

Stephen Koo
Stephen Koo

Reputation: 517

Thanks @mbdavis, his solution works correctly:

type IntentColorCategory = 'brand' | 'neutral' | 'semantic'
type IntentColor = 'primary' | 'secondary' | 'neutral' | 'error' | 'success'

const getByIntentColorCategory = <T extends any>({
  brand,
  neutral,
  semantic
}: Record<IntentColorCategory, T>) => (intent: IntentColor): T => {
  switch (intent) {
    case 'primary':
    case 'secondary':
      return brand;
    case 'neutral':
      return neutral;
    case 'error':
    case 'success':
    default:
      return semantic;
  }
}

type NumberIntents = Record<IntentColorCategory, number>
type StringIntents = Record<IntentColorCategory, string>

const numberIntents: NumberIntents = {
  brand: 1,
  neutral: 2,
  semantic: 3,
}

const stringIntents: StringIntents = {
  brand: 'a',
  neutral: 'b',
  semantic: 'c',
}

getByIntentColorCategory(numberIntents); // returns number
getByIntentColorCategory(stringIntents); // returns string

TS playground link

Upvotes: 0

mbdavis
mbdavis

Reputation: 4010

If I understand your question correctly - I think you're only off by where you're putting the generic parameter. It should go next to the arguments (on the function side, as opposed to the assignment side).

type IntentColorCategory = 'brand' | 'neutral' | 'semantic'
type IntentColor = 'primary' | 'secondary' | 'neutral' | 'error' | 'success'

const getByIntentColorCategory = <T extends any>({
  brand,
  neutral,
  semantic
}: Record<IntentColorCategory, T>) => (intent: IntentColor): T => {
  switch (intent) {
    case 'primary':
    case 'secondary':
      return brand;
    case 'neutral':
      return neutral;
    case 'error':
    case 'success':
    default:
      return semantic;
  }
}

You could then even drop the explicit type argument when using the function:

enter image description here

Upvotes: 1

Related Questions