I have the following component:
export enum Tags {
button = 'button',
a = 'a',
input = 'input',
}
type ButtonProps = {
tag: Tags.button;
} & ({ a?: string; b?: undefined } | { a?: undefined; b?: string }) &
JSX.IntrinsicElements['button'];
type AnchorProps = {
tag: Tags.a;
} & ({ a?: string; b?: undefined } | { a?: undefined; b?: string }) &
JSX.IntrinsicElements['a'];
type InputProps = {
tag: Tags.input;
} & ({ a?: string; b?: undefined } | { a?: undefined; b?: string }) &
JSX.IntrinsicElements['input'];
type Props = ButtonProps | AnchorProps | InputProps;
const Button: React.FC<Props> = ({ children, tag }) => {
if (tag === Tags.button) {
return <button>{children}</button>;
}
if (tag === Tags.a) {
return <a href="#">{children}</a>;
}
if (tag === Tags.input) {
return <input type="button" />;
}
return null;
};
// In this instance the `href` should create a TS error but doesn't...
<Button tag={Tags.button} href="#">Click me</Button>
// ... however this does
<Button tag={Tags.button} href="#" a="foo">Click me</Button>
This has been stripped back a little to be able to ask this question. The point is I am attempting a Discriminated Union along with intersection types. I am trying to achieve the desired props based on the tag value. So if Tags.button
is used then JSX's button attributes are used (and href
in the example above should create an error as it is not allowed on button
element) - however the other complexity is I would like either a
or b
to be used, but they cannot be used together - hence the intersection types.
What am I doing wrong here, and why does the type only work as expected when adding the a
or b
property?
Update
I've added a playground with examples to show when it should error and when it should compile.