Reputation: 41
Compiler tells me that properties propA
and propB
inside TestComponent
don't exist on type Props<T>
. Is there something I'm missing or misunderstanding about conditional types?
import React from 'react';
type PropsBase<T extends boolean | undefined> = {
isA?: T;
};
type PropsA = {
propA: string;
};
type PropsB = {
propB: string;
};
type Props<T extends boolean | undefined> = PropsBase<T> & (T extends false | undefined ? PropsB : PropsA);
function TestComponent<T extends boolean | undefined = true>(props: Props<T>) {
if (props.isA) {
return <>{props.propA}</>; // Property 'propA' does not exist
}
if (!props.isA) {
return <>{props.propB}</>; // Property 'propB' does not exist
}
return <></>;
}
<>
<TestComponent propA="propA" /> // Should be valid
<TestComponent isA propA="propA" /> // Should be valid
<TestComponent isA={false} propB="propB" /> // Should be valid
<TestComponent isA propB="propB" /> // Should be invalid
</>
My goal was to create an extendable and reusable type which properties can be controlled through generics. I know this is also done with unions, but it's not quite easy to build other types on top of it.
Upvotes: 1
Views: 207
Reputation: 8718
I think your first example of <TestComponent propA="propA" />
should also be invalid? After all, in that case, ìsAis of type
undefined`.
I changed your Props<T>
into this Props
instead:
type Props = ({ isA: true } & PropsA) | ({ isA?: false } & PropsB);
function TestComponent(props: Props) {
if (props.isA) {
return props.propA; // OK
}
if (!props.isA) {
return props.propB; // OK
}
}
Your usage of it is also validated properly now:
TestComponent({ propA: 'propA' }); // Property 'isA' is missing
TestComponent({ propB: 'propB' }); // OK
TestComponent({ isA: true, propA: 'propA' }); // OK
TestComponent({ isA: false, propB: 'propB' }); // OK
TestComponent({ isA: true, propB: 'propB' }); // 'propB' does not exist
Interestingly, if I use prop.isA === true
, TypeScript behaves better:
function TestComponent(props: Props) {
if (props.isA === true) {
return props.propA; // OK
}
const isA = props.isA; // type `false | undefined`
if (!props.isA) {
return props.propB; // OK
}
}
That should be good enough in your case I hope? Although I'm surprised in the difference between if (props.isA)
and if (props.isA === true)
. Mind that this requires TypeScript to run in strict mode, with noImplicitAny
enabled to be specific. Otherwise you need to use if (props.isA === true)
. Weird, but running with noImplicitAny
disabled is very rare anyway.
Upvotes: 1