I use the following type as property for my component:
type MapProps = {
standalone: true
} | {
standalone: false
inContextOf: Nullable<Artifact | ExcavationSite>
fieldSetter: UseFormSetValue<FieldValue<any>>
}
Further down, the following checks are performed:
if ((props.standalone || !props.standalone && !props.inContextOf) || props.inContextOf && !IsEntityArtifact(props.inContextOf))
return;
if (props.inContextOf?.Latitude == 0.0 && props.inContextOf.Longitude == 0.0)
return;
const newPoint = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [props.inContextOf!.Longitude, props.inContextOf!.Latitude]
}
};
Everything is working as expected with this implementation, however as soon as i move those checks to a function like this
const CheckReturnScenarios = (): boolean => {
if ((props.standalone || !props.standalone && !props.inContextOf) || props.inContextOf && !IsEntityArtifact(props.inContextOf))
return true;
if (props.inContextOf?.Latitude == 0.0 && props.inContextOf.Longitude == 0.0)
return true;
return false;
};
and I use it like this
if (CheckReturnScenarios())
return;
const newPoint = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [props.inContextOf!.Longitude, props.inContextOf!.Latitude]
}
};
I do get the following error: TS2339: Property inContextOf does not exist on type MapProps Property inContextOf does not exist on type { standalone: true; }
I'm wondering why it's suddenly no longer possible for TypeScript to determine that the properties being accessed must exist here, because otherwise the check performed above would abort the function.
Would be nice if someone could shed some light on this.
Thank you!
Edit:
The entire component where the error occurs:
import { FC, useState, useEffect } from 'react';
type Person = {
Name: string;
Age: number;
Kind: number;
};
type Product = {
Title: string;
Description: string;
Kind: number;
};
type ComponentProps =
| {
standalone: true;
}
| {
standalone: false;
inContextOf: Nullable<Person | Product>;
fieldSetter: Function;
};
export const MyTestComponent: FC<ComponentProps> = (props) => {
//#region Constants
//#endregion
//#region States
//#endregion
//#region Hooks
useEffect(() => {
Initialize();
}, []);
//#endregion
//#region Functions
const CheckAbortScenarios = (): boolean => {
if (
props.standalone ||
(!props.standalone && !props.inContextOf) ||
(props.inContextOf && !IsEntityPerson(props.inContextOf))
)
return true;
if (props.inContextOf?.Kind == 0.0) return true;
return false;
};
function IsEntityPerson(entity: Person | Product): entity is Person {
return (entity as Person).Name !== undefined;
}
const Initialize = (): void => {
if (CheckAbortScenarios()) return;
/*The error happens here*/
const kind = props.inContextOf!.Kind;
};
//#endregion
//#region Render
return <div></div>;
//#endregion
};
Since you're moving the type checking logic to a separate function, you're looking for the type guarding is
operator.
In the code that you showed at the bottom you are already using it for the IsEntityPerson()
function.
Revisiting your ComponentProps
type, we should first separate each case in the union into separate types. I actually would go as far as replacing Nullable<Person | Product>
logic into another union where inContextOf
could be null
or Person | Product
, as such:
type SubTypeA = {
standalone: true
}
type SubTypeB = {
standalone: false
inContextOf: null
fieldSetter: Function
}
type SubTypeC = {
standalone: false
inContextOf: Person | Product
fieldSetter: Function
}
type ComponentProps = SubTypeA | SubTypeB | SubTypeC
Now, your type guard function should receive the variable that needs type checking as an argument and its return type should be a argument is MyType
. This basically discrimates argument
into the type MyType
if the function's return is true
, otherwise it turns into Exclude<typeof argument, MyType>
.
Given this, you should probably separate this type guard into two functions:
const CheckSubtypeA = (props: ComponentProps): props is SubTypeA => {
return props.standalone
}
const CheckSubtypeB = (props: ComponentProps): props is SubTypeB => {
return (
CheckSubtypeA(props) ||
(!props.standalone && !props.inContextOf) ||
(props.inContextOf && !IsEntityPerson(props.inContextOf)) ||
props.inContextOf?.Kind == 0.0
)
}
And finally, inside your Initialize
function:
const Initialize = (): void => {
if (CheckSubtypeA(props) || CheckSubtypeB(props)) return
const kind = props.inContextOf.Kind // no longer errors and no need for a null assert (!)
}