How to select subtype in typescript (if the field is optional)

Viewed 258

I have following type in Typescript:

export type SomeQuery = {
  __typename?: 'Query'
  viewer?:
    | {
        __typename?: 'Viewer'
        books?:
          | {
              __typename?: 'UserBooks'
              total?: number | undefined | null
              collection: Array<{+
                __typename?: 'UserBook'
                book?:
                   | {
                      __typename?: 'Book'

Since I have the type of the query generated by graphql, I would like to extract the type of a book. I know in TS you can do something like:

type Viewer = SomeQuery['viewer']

This works, the problem is going for the next level, since viewer is optional:

type MyBooks = Viewer['books']

I get as an error:

TS2339: Property 'books' does not exist on type '{ __typename?: "Viewer" | undefined; books?: { __typename?: "UserBooks" | undefined; total?: number | null | undefined; collection: { __typename?: "UserBook" | undefined; book?: { ...;

How can I then get the subtypes if the fields are optional?

PS: according to the answer I manage to get the nested type fields::

type Viewer = NonNullable<MyEventsQuery['viewer']>
type Books = NonNullable<Viewer['books']>
type Collection = NonNullable<Books['collection']>

The problem is now with type Collection, which is an array. I tried:

export type MyBook = Collection['book']
export type MyBook = Collection[number]['book']

The error is:

TS2339: Property 'book' does not exist on type '{ __typename?: "UserBook" | undefined; book?: { __typename?: "Book" | undefined; ... | ... 1 | undefined; } | null | undefined; }[]'.

I think the issue is because of the [] at the end, that indicates that is type array of UserBook, but I don't know how to get then the type within the array.

Could someone point me to what I should use in the documentation, in order to extract the type in the array?

2 Answers

You can apply NonNullable utility to Viewer before looking up books.

type MyBooks = NonNullable<Viewer>['books']

Q: Could someone point me to what I should use in the documentation, in order to extract the type in the array?

A: Don't know where in the docu. But you can build a conditional type that ends up with a value that is not an array.

(Basicly the same as NonNullable, which ends up with an entry that is not null and not undefined.)

/**
 * @description Returns the type of a given input. If the input was an array, it
 * returns the returns the type inside the array. Non Recursive.
 *
 * @example type result = UnwrapArrayType<[1,2,3]>
 * result here is of type number.
 *
 * @example type result = UnwrapArrayType<5>
 * result here is of type number.
 */
export type UnwrapArrayType<T> = T extends (infer U)[] ? U : T

And then throw it at your collection.

type UserBook = UnwrapArrayType<Collection>

Would be interested in the approach you took to solve it, since it is 1 month old.

Greetings

Related