'How to access nested conditional type in typescript
The origin of the following autogenerated type is a GraphQL query:
export type OfferQuery = { __typename?: 'Query' } & {
offer: Types.Maybe<
{ __typename?: 'Offer' } & Pick<Types.Offer, 'id' | 'name'> & {
payouts: Array<
{ __typename?: 'Payout' } & Pick<
Types.Payout,
'weGet' | 'theyGet'
> & {
offer: { __typename?: 'Offer' } & Pick<Types.Offer, 'id'>;
publisher: Types.Maybe<
{ __typename?: 'Publisher' } & Pick<Types.Publisher, 'id'>
>;
eventType: Types.Maybe<
{ __typename?: 'EventType' } & Pick<
Types.EventType,
'id' | 'name'
>
>;
}
>;
}
>;
};
Now I would like to reuse parts of the OfferQuery type in my react component, namely a payout element.
type Payout = OfferQuery['offer']['payouts'][number];
This however gives me an error "Property 'payouts' does not exist on type 'Maybe{ __typename?: "Offer" | undefined; }..."
.
Do you have any suggestions on how to circumvent this issue and access payouts definition anyway?
tsconfig.json
{
"compilerOptions": {
"esModuleInterop": true,
"isolatedModules": true,
"jsx": "react-jsx",
"module": "esnext",
"moduleResolution": "node",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext"
}
}
Typescript 4.1.3
Solution 1:[1]
TL;DR
type Payout = NonNullable<OfferQuery['offer']>['payouts'][number];
because
Maybe
causesOfferQuery['offer']
to be nullable, and so you need to wrap it with theNonNullable
type util first:
Interpreting the error-message
The error-message is unclear, because it does not reveal what Maybe
actually means.
The definition of Maybe
, will say:
type Maybe<T> = T | null;
meaning when you try to get payouts
from OfferQuery['offer']
, you're also trying to get payouts
from null
(which doesn't exist). This is what the error-message is trying to communicate.
GraphQL codegen and nullable
GraphQL codegen will (correctly) add Maybe
to each field that is nullable in your GraphQL schema. This has the implication that when you use the type OfferQuery['offer']
you will get both null
AND the actual definition.
I.e. you're actually receiving null
together with the rest:
type ExampleWithMaybe = null | {
payouts: { weGet: number; theyGet: number }[];
/* ...the rest of the definition... */
};
instead of receiving what you likely expected:
type ExampleWithoutMaybe = {
payouts: { weGet: number; theyGet: number }[];
/* ...the rest of the definition... */
};
So when you want to use the type payouts
on OfferQuery['offer']
, you need to remember that you have to exclude the null
-value first, because payouts
doesn't exist on null
.
The solution: Remove null
In order to get the type definition without null
, you can use the type-util NonNullable
, meaning we can rewrite ExampleWithoutMaybe
from the above example like so:
type ExampleWithMaybe = null | {
payouts: { weGet: number; theyGet: number }[];
/* ...the rest of the definition... */
};
type ExampleWithoutMaybe = NonNullable<ExampleWithMaybe>;
and to make it specific to your example:
// ? `payouts` doesn't exist on `null`
// type Payout = OfferQuery['offer']['payouts'][number];
// ?
type Payout = NonNullable<OfferQuery["offer"]>["payouts"][number];
Solution 2:[2]
Apparently the only solution is to exclude null and undefined before accessing the array element type:
type Payout = Exclude<OfferQuery['offer']['payouts'], null | undefined>[number];
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Patrick Eriksson |
Solution 2 | chillyistkult |