Define type based on value of keys in an object

Viewed 87

I am adding one Select component that has the following structure.

type Option = {
  value: string | number
  label: string;
  icon?: string
}
type SelectProps = {
  labelKey?: string;
  iconKey?: string;
  valueKe?: string;
  options: Option[]
}

function Select({
  labelKey = 'label',
  iconKey = 'icon',
  valueKey= 'value',
  groupBy = 'groupBy',
  options// need to type Option
}: SelectProps) {
  // some logic to render options
  // options.map(option => (<li key={option[valueKey]}>{option[labelKey]}</li>))
}

Here, options is an array of options and I am trying to give flexibility to users to provide keys to use for labels, icons etc so the user doesn't need to map data all the time.

For now, the Option type has hardcode keys like label, value, icon but I want to create this type based on values passed to labelKey, valueKey, iconKey etc. For example, if a user passes the labelKey="name" prop then the Option type should allow the following data:

[ { 
  name: 'Product',
  value: 'product',
  icon: 'product'
}]

So far I have tried the following implementation but it sets all keys' types to string.

type OptionKeys = {labelKey: string, valueKey: string, iconKey: string}
type Option<T extends OptionKeys> = {
    [label in T["labelKey" | "valueKey" | "iconKey"]]: string // all the types are string
}

type SelectProps<T extends OptionKeys = {
    labelKey: 'label',
    valueKey: 'value',
    iconKey: 'icon'
}> = {
    labelKey?: string
    valueKey?: string;
    iconKey?: string;
    options: Option<T>[]
}

Here, the Option's keys have value of type string but I want to define type based on key. For example, if the key is labelKey, I want its value to be number | string etc.

One option, I see here is to accept OptionType from outside by making the Select component generic but in that case, I need to refactor my component and want to avoid this refactoring at the moment.

How can I update my type to handle this scenario?

1 Answers

Using the FromEntries type from this blog post: https://dev.to/svehla/typescript-object-fromentries-389c,

Define the SelectProps type as follows:

type SelectProps<LK, VK, IK> = {
    labelKey?: LK
    valueKey?: VK
    iconKey?: IK
    options: FromEntries<[
        [LK, string], 
        [VK, string | number], 
        [IK, string | undefined]
    ]>
}

We have three keys in the options, the label key (LK) which is a string, the value key (VK) which is a string | number and the icon key (IK) which is a string | undefined

Now we define the Select function as follows:

function Select<
    LK extends string = 'label', 
    VK extends string = 'value', 
    IK extends string = 'icon'
>(props: SelectProps<LK, VK, IK>) {}

It is important to put the default key names on the function itself rather than the SelectProps type. I am not sure why.

Full playground link

Related