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!