I'm trying to develop a REACT component using TypeScript, and to be more precise, using discrimination union types.
What I want to achieve is that the props of my component can have the context prop, and, if it does, then the name props is of type keyof T. Otherwise, name is a simple string.
Here's the first example:
interface IBaseProps {
label: string;
}
interface IWithContext<T> extends IBaseProps {
context: T;
name: keyof T;
}
interface IWithoutContext extends IBaseProps {
name: string;
}
type IProps<T> = IWithContext<T> | IWithoutContext;
const TestComp = <T extends {}>(props: IProps<T>) => {
const testMethod = () => {
if (props.context) { // Property 'context' does not exist on type 'IProps<T>'. Property 'context' does not exist on type 'IWithoutContext'.
console.log('Test_1');
}
}
return <p>Hello World</p>
}
In this case, the issue is that the property context is not in IWithoutContext. Easy fix: add the context in that interface as well with null type.
interface IBaseProps {
label: string;
}
interface IWithContext<T> extends IBaseProps {
context: T;
name: keyof T;
}
interface IWithoutContext extends IBaseProps {
context: never;
name: string;
}
type IProps<T> = IWithContext<T> | IWithoutContext;
const TestComp = <T extends {}>(props: IProps<T>) => {
const testMethod = () => {
if (props.context !== null) { // OK
if (props.context[props.name]) { // Type 'string | keyof T' cannot be used to index type 'T'.
console.log('Test_1');
}
}
}
return <p>Hello World</p>
}
But now, I've a different error: apparently, typescript is not able to understand that props.name is of type keyof T, based on the fact that props.context is different from null.
Though, I think that, due to the interfaces, it should be inferred that props.name is of type keyof T.
What am I missing here?
EDIT: here's a more comprehensive example.
interface IValidationRule {
id: string;
}
interface IValidation {
[key: string]: IValidationRule[];
}
class MyCustomClass {
validation: IValidation;
}
interface IBaseProps {
label: string;
}
interface IWithContext<T> extends IBaseProps {
context: T;
name: keyof T;
}
interface IWithoutContext extends IBaseProps {
context: never;
name: string;
validation: IValidation;
}
type IProps<T> = IWithContext<T> | IWithoutContext;
const TestComp = <T extends MyCustomClass>(props: IProps<T>) => {
const testMethod = () => {
let validation : IValidation[] = null;
/* In case context is defined, I can use 'name' to retrieve the validation. */
if (props.context !== null) {
validation = props.context.validation[props.name];
}
/* Otherwise, if context is null, validation shuold be provided in the props. */
else {
validation = props.validation;
}
}
return <p>Hello World</p>
}
Apparently, the problem is that type discrimination does get along with generic type T or null value.
By the way, the real example is really like this.. Just, the class has more properties.
Also, I've tried the hasProperty approach, and it works great!