I'm a novice in Typescript and I'm trying to create React components with different variants, IE a Button with a 'foo' and an 'bar' variant.
I would like to use the component <Button variant="foo" /> for which I'd use an interface like so:
interface Props {
variant: 'foo' | 'bar';
prop1?: boolean;
prop2?: boolean;
}
When creating a Button with the prop variant="foo" I want to allow certain props, and disable others. Same with the other variants. For this reason I created interfaces like so which is the result of many google / stackoverflow searches:
interface Props {
variant?: never;
prop1?: boolean;
prop2?: boolean;
}
type Variant = 'foo' | 'bar' | 'baz';
interface DefaultVariant extends Omit<Props, 'variant'> {
variant: Variant;
}
interface FooVariant extends DefaultVariant {
variant: 'foo';
prop1?: never;
}
interface BarVariant extends DefaultVariant {
variant: 'bar';
prop1?: never;
prop2?: never;
}
interface BazVariant extends DefaultVariant {
variant: 'baz';
prop1?: never;
prop3: boolean;
}
...in order to use function overloads like so:
export default function Component(props: Props): ReactElement;
export default function Component(props: BazVariant): ReactElement;
export default function Component(props: BarVariant): ReactElement;
export default function Component(props: FooVariant): ReactElement;
export default function Component(props: Props | BarVariant | BazVariant | FooVariant): ReactElement {
const ComponentVariant = variants[props.variant || 'foo']
return (
<ComponentVariant />
)
}
const Foo = () => <div>Foo</div>
const Bar = () => <div>Bar</div>
const Baz = () => <div>Baz</div>
type Variants = {[key in Variant]: (props: any) => ReactElement };
const variants: Variants = {
foo: Foo,
bar: Bar,
baz: Baz,
}
Hoping to use the component like so:
function App(): ReactElement {
return (
<>
{/* should work: */}
<Component />
<Component prop1 prop2/>
<Component variant="foo" prop2 />
<Component variant="bar" />
<Component variant="baz" prop2 prop3 />
{/* should fail on prop1: */}
{/* this works */}
<Component variant="foo" prop1 prop2 />
{/* should fail on presence of prop1 and prop2: */}
{/* but fails on variant and prop1: */}
{/* No overload matches this call.
The last overload gave the following error.
Type '"bar"' is not assignable to type '"foo"'.
Type 'true' is not assignable to type 'undefined' */}
<Component variant="bar" prop1 prop2 />
{/* should fail on presence of prop1 and lack of prop3: */}
{/* but fails on variant: */}
<Component variant="baz" prop1 prop2 />
</>
)
}
And while this sort of works, in the sense that I cannot create Component without the correct props, the point of failure as determined by Typescript is not correct.
For example <Component variant="bar" prop1 prop2/> throws "No overload matches this call." as expected, but it erroneously states that 'bar' is not assignable to type 'foo', where the problem actually lies in the assignment of prop1 and prop2 that are not allowed in the BarVariant interface.
My problem, as far as I can tell, lies in the fact that Typescript always seems to infer the last overload, where I expected (and would like!) it to infer the overload related to the "variant" prop.
If anyone could share some light on what I'm doing wrong here it would be very much appreciated!