How do I make sure my functions components props are mutually exclusive, with code completion?
My code:
type Variant = 'a' | 'p' | 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'button'
interface TypographyProps {
children: React.ReactNode | string
className?: string | string[]
variant?: Variant
}
type Text = TypographyProps
type Primary = TypographyProps & { primary: true }
type Secondary = TypographyProps & { secondary: true }
type Extra = TypographyProps & { extra: true }
type Dark = TypographyProps & { dark: true }
type Muted = TypographyProps & { muted: true }
function Typography(props: Text): JSX.Element;
function Typography(props: Primary): JSX.Element;
function Typography(props: Secondary): JSX.Element;
function Typography(props: Extra): JSX.Element;
function Typography(props: Dark): JSX.Element;
function Typography(props: Muted): JSX.Element;
function Typography({ children, className, variant, ...props }: TypographyProps): JSX.Element {
}
In WebStorm, when using control+spacebar (code completion), I don't get hints for props like primary or secondary.
When I use more than one prop, I always get the error the first prop doesn't exist on type Muted.
e.g.
<Typography primary secondary>Foo</Typography>
Just want to extend @jcalz's answer for general usage:
type MutuallyExclude<T, E extends keyof T> =
| {
[K in E]: { [P in K]: T[P] } & Omit<T, E> & {
[P in Exclude<E, K>]?: never;
} extends infer O
? { [P in keyof O]: O[P] }
: never;
}[E]
| ({ [K in E]?: never } & Omit<T, E>);
And the usage would like:
interface TypographyProps {
primary: true;
secondary: true;
}
type ExclusiveProps = 'primary' | 'secondary';
const Typography = (props: MutuallyExclude<TypographyProps, ExclusiveProps>) => {
...
};
const Case1 = () => <Typography primary />; // fine
const Case2 = () => <Typography />; // fine
const Case3 = () => <Typography primary secondary />; // error
While the error message is hard to be understand. I will update the answer if I figure out a better solution, or maybe it will be complement in the comment.
If one of the properties must be assigned, the simplest generic type would be:
type MutuallyExclude<T, E extends keyof T> = {
[K in E]: { [P in K]: T[P] } & {
[P in Exclude<E, K>]?: never;
} & Omit<T, E>;
}[E];
The usage is as same as above, but the second case will return error:
interface TypographyProps {
primary: true;
secondary: true;
}
type ExclusiveProps = 'primary' | 'secondary';
const Typography = (props: MutuallyExclude<TypographyProps, ExclusiveProps>) => {
...
};
const Case1 = () => <Typography primary />; // fine
const Case2 = () => <Typography />; // error
const Case3 = () => <Typography primary secondary />; // error
In my case, it is used for customized RadioGroup since I want it can be described by either options or children. While it would be pretty ambiguous when both of them are assigned.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With