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" />
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.
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.