Why this keyof doesn't infer correctly?

I’ve code like this (TS Playground):

const createComp = <T extends {
  modifiers:  {
    [key: string]: string;
  };
  defaultMods: keyof T["modifiers"];
}
>(com: T) => com;

const style = createComp({
  modifiers: {
    blue: 'blue',
    red: 'red'
  },
  defaultMods: 'red' // correct. defaultMods type is 'blue' | 'red'
});

It infer defaultMods type to 'blue' | 'red' as I expected.

But when I try to extract the extends type to Component type, like this (TS Playground):

type Component = {
  modifiers:  {
    [key: string]: string;
  };
  defaultMods: keyof Component["modifiers"];
}

const createComp = <T extends Component>(com: T) => com;

const style = createComp({
  modifiers: {
    blue: 'blue',
    red: 'red'
  },
  defaultMods: '' // defaultMods type is string
});

It doesn’t return defaultMods type as 'blue' | 'red'. Did I miss something?

>Solution :

keyof Component["modifiers"] always resolves to string. You will need an extra type parameter to Component to represent the modifiers, and infer that in your function:

type Component<TModifiers extends Record<string, string>> = {
  modifiers: TModifiers;
  defaultMods: keyof TModifiers;
}


const createComp = <TModifiers extends Record<string, string>>(com: Component<TModifiers>) => com;


const style = createComp({
  modifiers: {
    blue: 'blue',
    red: 'red'
  },
  defaultMods: '' // it should be 'blue' | 'red'
});

Playground Link

Leave a Reply