I have experimented with creating custom types recently and doing so, I came across an error which I really do not understand. Assume the following setup:
Type Definitions
type Error = {
error: string
}
type Item = {
id: string
name: string
}
type Result = Item | Error
Test Function
const testFunc = ( item: boolean ): Result => {
return item ? { id: '0x1', name: 'My Item' } : { error: 'An error has occurred };
}
Progam
const item = testFunc(true);
if ( item.error ) { // <-- TS complains that `error` does not exist on type `Result`
console.log('error');
}
console.log('Happy days!', item)
TS keeps complaining that
Property 'error' does not exist on type 'Response'.
Property 'error' does not exist on type 'Item'.ts(2339)
If I remove Item
from Result
it obviously works. Do I have something misconfigured or do I get this completely wrong? I have basically read through here and thought that this should be a very easy task.
Thanks! Any help appreciated!
CodePudding user response:
When you get a type that has union of different types and you are trying to access it - the type system needs to know which type of "this multi-types / unions do you mean?" because it cannot know what are trying to reference.
You can one of this solutions below:
TypeCasting the variables when you want to access it as error
if ((item as Error).error) {}
TypeCasting the return
const item = testFunc(true) as Error
CodePudding user response:
By specifying that the return type of the function is Result
, the returned item
is inferred to be (due to how you've specified the type):
{ error: string }
| { id: string, name: string }
TypeScript doesn't go into the local paths of the function and narrow down the type of the possible return value, not without overloading or generics or something.
You can fix this by removing your annotated type-widening so that TS can infer that the return value is of type:
{
id: string;
name: string;
error?: undefined;
} | {
error: string;
id?: undefined;
name?: undefined;
}
Which is easier to work with than your original types, because possibly having properties whose values are undefined
means that those properties are easily accessible. (In contrast, something like { id: string; name: string }
does not have the error
property at all, so error
can't be accessed on it without narrowing down the type first, like with in
)
I'd also recommend not using type Error
- don't shadow the global Error
, call it something else.
const testFunc = (item: boolean) => {
return item ? { id: '0x1', name: 'My Item' } : {
error: 'An error has occurred'
};
}
will compile successfully.
CodePudding user response:
You need a type guard before accessing a property or a method that is not common to all the types in your Union Type.
In this case, the easiest would probably to check whether your item
has a property named error
:
const item = testFunc(true);
if ( 'error' in item ) {
console.log(item.error);
} else {
console.log('Happy days!', item)
}
If you were using classes instead of types, you could also use if(item instanceof MyError)
instead of if ( 'error' in item )
.
If you types were primitive (boolean, string, number), you could also use if(typeof item === 'string')
instead of if ( 'error' in item )
.
BTW, as @CertainPerformance mentioned, don't use Error
.