I Have the following check logic that checks from a certain type and a component that gets props there are 2 types of props mandatory and optional
in this current case the wight props is optional the issue is the isCurrentType function returns 2 types and the el.weight is erroring out since the other type has no wight on it I have this feeling that I am kind of over complicating the architecture.
const CurrentView: React.FC<Props> = () => {
cosnt items = useAppSelector((state) => state.data.itemsFromDB);
type TypeA = {id:string; equipment:string; weight:string}
type TypeB = {id:string; equipment:string;}
export const isCurrentType = (item: any): item is TypeA | TypeB => {
return ['typeA', 'TypeB'].includes(item?.itemType);
};
const renderItems = items.map((el) => {
if (isCurrentType(el)) {
return (
<Component
id={el.id}
equipment={el.equipment}
weight={el.weight/*Erorr on el.weight*/ ? el.weight : undefined}
/>
);
}})
return(<div>{renderItems}</div>)
}
CodePudding user response:
The compiler will not let you access properties on a union type unless every member of the union has a known property at that key:
type U = { id: string, weight: string } | { id: string }
declare const u: U;
u.id.toUpperCase(); // okay
u.weight // error! Property 'weight' does not exist on type '{ id: string; }'.
This is because object types in TypeScript are not sealed or exact. It is technically possible for a value of type {id: string}
to contain extra properties like weight
(although excess property checking can complicate this):
const something = { id: "abc", weight: 123 }
const oops: U = something; // this is accepted
That compiles because someThing
matches {id: "abc"}
, and weight
of type number
doesn't invalidate that. You were assuming that a value of type U
either had a string
-valued weight
property, or was missing the weight
properly entirely. But the assignment const oops: U = something
shows that this assumption is technically incorrect, and that's the problem you're running into.
There are a few ways around this. You could explicitly prohibit weight
on the second union element, by making it an optional property whose value is the impossible never
type:
type U = { id: string, weight: string } | { id: string, weight?: never }
// now weight is known in every member of the union --> ^^^^^^^^^^^^^^
declare const u: U;
u.weight // okay, string | undefined
const something = { id: "abc", weight: 123 }
const oops: U = something; // error!
That works now; the type of u.weight
is known to be either string
or undefined
. And the compiler catches the assignment of something
to oops
because now {id: string, weight: number}
is no longer assignable to U
. That is, now it is correct to assume that weight
is either present and string
-valued, or absent (more or less).
A less intrusive fix is to use a narrowing technique that makes the same technically-incorrect assumption as you were making. The compiler will treat use of the in
operator as a way to narrow a union-typed value to those union members known to have the checked property:
"weight" in u ? u.weight : undefined // okay, string | undefined
When the "weight" in u
check is true
, the compiler assumes that u.weight
is of type string
, so it lets you evaluate u.weight
. This narrowing is technically unsafe, so this method is only appropriate if you can be relatively sure that the oops
assignment will happen. After all, that still compiles without error:
const something = { id: "abc", weight: 123 }
const oops: U = something; // still no error
But as long as your code doesn't do things like that, in
-operator narrowing should work for you.