Hello I bumped into a situation where I want to conditionally type my Component by its props, but TypeScript works terribly with generics in that case.
Could you explain to me why specifically with Component, TypeScript works bad, but if I try to create a variable const test:Props<{variant:"link"}>
, it will infer type correctly?
import React from 'react';
type BaseProps = {
variant: 'close' | 'link' | 'dropdown';
text: string;
};
type CloseButtonProps = BaseProps & {
onClose: () => void;
onClick: () => void;
};
type LinkButtonProps = BaseProps & {
href: string;
isExternal?: boolean;
};
type DropdownButtonProps = BaseProps & {
content: string;
};
type Props<T extends BaseProps> = T['variant'] extends 'close'
? CloseButtonProps
: T['variant'] extends 'link'
? LinkButtonProps
: DropdownButtonProps;
const MenuItem = <T extends BaseProps>(props: Props<T>) => {
return null;
};
const element = <MenuItem variant="link" href="test" text="test"/>
As you can see, element
cannot have href
prop because it falls to DropdownProps
, why?
2
Answers
In your case you can simply use a Discriminated Union, instead of chained Conditional Types:
You can now directly use the Union, without resorting to generic type parameter inference:
Playground Link
Note that in your example,
const test:Props<{variant:"link"}>
works because you explicitly specify the generic type parameter; it is not "inferred"; but then the conditional types indeed work.If we explicitly specify the generic type parameter in the JSX in a similar way, it works as well:
But even outside JSX, should let TS teying to infer the generic type parameter, it fails as well:
Simply Tweaking your
Props
andMenuItem
so that the generic type has ‘close’ | ‘link’ | ‘dropdown’ as parameters instead of completeBaseProps
object will make it work:Playground