Reputation: 36836
I want to return an object as fetch response that can have one of the property (data or mes):
{ data: Data } | { mes: ErrMessage }
Problem is that typescript complains about this object, let's say props:
if (prop.mes) return // Property 'mes' does not exist on type '{ data: string; }'.
prop.data // Property 'data' does not exist on type '{ mes: string; }'.
Is there any alternative except (it's not convenient to define this big type in component every time):
{ data: Data, mes: false } | { mes: ErrMessage, data: false}
Typescript playground example.
type ErrMessage = string // some complex structure
const fetchAPI = <Body = any, Data = any>(link: string, body: Body): Promise<{ data: Data } | { mes: ErrMessage }> => {
return new Promise((resolve) => {
fetch(link, { body: JSON.stringify(body) })
.then((raw) => raw.json())
.then((res) => {
resolve(res.err ? { mes: res.err } : { data: res })
})
.catch((err) => {
resolve({ mes: 'text' })
})
})
}
type Data = string // some complex structure
type MyReq = string // some complex structure
type MyRes = {data: Data } | {mes: ErrMessage}
const someLoader = async () => {
const res = await fetchAPI<MyReq, MyRes>('reader', 'body')
return {res}
}
const componentThatGetProp = (prop: MyRes ) => {
// error handler
if (prop.mes) return // Property 'mes' does not exist on type '{ data: string; }'.
// do something
prop.data // Property 'data' does not exist on type '{ mes: string; }'.
}
Upvotes: 1
Views: 1323
Reputation: 328097
The "right" type for TypeScript to use is
type MyRes = { data: Data, mes?: undefined} | { mes: ErrMessage, data?: undefined}
where the irrelevant properties in each member of the union are optional and have an undefined
value, as opposed to a false
value. (Or you could use never
instead of undefined
, since optional properties always get | undefined
added to them, and never | undefined
is just undefined
.) That corresponds to what will actually happen if you check an irrelevant property: it will be missing. As of TypeScript 3.2, the compiler treat the above type as a discriminated union and your errors will go away:
const componentThatGetProp = (prop: MyRes) => {
// error handler
if (prop.mes) return // okay
// do something
prop.data // okay
}
I agree that it would be a pain to manually turn a union type your existing MyRes
into one where each union member contains all the keys from all the other union members. Luckily you don't need to do this manually. TypeScript's conditional types let you programmatically manipulate a "regular" union into the discriminated form above. Here's one way to do it, using a type alias called I'll call ExclusifyUnion<T>
to indicate that it takes a union type T
and turns it into a new union type where the compiler can tell that any value of that type must match one member of the union exclusively:
type AllKeys<T> = T extends any ? keyof T : never;
type ExclusifyUnion<T, K extends AllKeys<T> = AllKeys<T>> =
T extends any ?
(T & { [P in Exclude<K, keyof T>]?: never }) extends infer U ?
{ [Q in keyof U]: U[Q] } :
never : never;
I can explain that if you want, but the sketch is this: AllKeys<T>
returns all keys from a union T
, so AllKeys<{a: string} | {b: number}>
is "a" | "b"
. And ExclusifyUnion<T>
takes each member of the union and intersects it with a type containing all the rest of the keys as optional properties of type never
. So ExclusifyUnion<{a: string} | {b: number} | {c: boolean}>
will end up looking like {a: string, b?: never, c?: never} | {b: number, a?: never, c?: never} | {c: boolean, a?: never, b?: never}
. Here's what it does to MyRes
:
type MyRes = ExclusifyUnion<{ data: Data } | { mes: ErrMessage }>
/*type MyRes = {
data: string;
mes?: undefined;
} | {
mes: string;
data?: undefined;
}*/
And this should scale up easily even if MyRes
has more union members with more properties.
Okay, hope that gives you a path forward. Good luck!
Upvotes: 2
Reputation: 44086
Instead of
if (prop.mes) return
just try
if ('mes' in prop) return
one tries to access the property (which might not be there), the other checks for it's existence
Upvotes: 5