Home > front end >  How to derive a type from another type in typescript
How to derive a type from another type in typescript

Time:10-11

How can I dynamically define the type of an attribute based on the value of another one in Typescript?

Example: I have this type Theme, and would like the appearance types to be a union of possibilities based on the variantType attribute:

type Theme = {
  button: {
    variantType: {
      normal: { appearance1: { color: 'red' } };
      success: { appearance1: { color: 'red' }, appearance2: { color: 'blue' } };
      newVariant: { anyNewAppearance: { color: 'orange' } };
    };
  };
};

type VariantType<Component extends keyof Theme> =
  'variantType' extends keyof Theme[Component]
    ? keyof Theme[Component]['variantType']
    : unknown;

type VariantAppearance<Variant extends keyof Theme['button']['variantType']> =
  keyof Theme['button']['variantType'][Variant];

type ButtonProps = {
  variantType: VariantType<'button'>;
  appearance: VariantAppearance<ButtonProps['variantType']>; // Here is the problem
};

Example of expected results (Not allowed I mean a typescript error):

// Allowed, variantType="normal" allows appearance "appearance1"
<Button variantType="normal" appearance="appearance1" />

// NOT allowed, variantType="normal" allows only appearance "appearance1"
<Button variantType="normal" appearance="appearance2" />

// NOT allowed, variantType="newVariant" allows only appearance "anyNewAppearance"
<Button variantType="newVariant" appearance="appearance2" />

// Allowed, variantType="newVariant" allows appearance "anyNewAppearance"
<Button variantType="newVariant" appearance="anyNewAppearance" />

Currently it's of type never Currently it's of type never

CodePudding user response:

To derive the type, you could pass in the Variant as a generic of ButtonProps. Then, when defining your Button component, infer the generic from the parameters.

type ButtonProps<T extends VariantType<'button'>> = {
    variantType: T;
    appearance: VariantAppearance<T>;
};

function Button<T extends VariantType<'button'>>(props: ButtonProps<T>) {
    return <></>;
}

which should get the result you are looking for.

enter image description here

A second way to do so without making ButtonProps generic is to make ButtonProps a union of possibilities like so:

type GenerateButtonProps<V extends VariantType<'button'>> = V extends any ? {
    variantType: V;
    appearance: VariantAppearance<V>
} : never;

type ButtonProps = GenerateButtonProps<VariantType<'button'>>;

function Button(props: ButtonProps) {
    return <></>;
}

Although, this method will produce much more verbose error messages. enter image description here

  • Related