Graphql-code-generator: How to specify types for nested `Maybe`s in generated query types?

Created on 6 Dec 2019  Â·  14Comments  Â·  Source: dotansimha/graphql-code-generator

(This is basically the same as https://github.com/dotansimha/graphql-code-generator/issues/713#issuecomment-474884827)

When accessing nested generated query types I'm not sure how to specify types wrapped behind Maybe. Per the comment in ^, the suggested solution was nested type access a la MyType['fieldA']['fieldB'] but this doesn't seem to work for Maybe.

I know in Haskell / other typed functional langs they usually use lenses / optics (particularly prisms) to get around this problem. Is there an existing solution for this in graphql-code-generator or is it on the roadmap?

Thanks!

enhancement plugins

Most helpful comment

I'm also having this exact same issue, where it's outputting Array<Maybe<Post>>, which means it can be a null object within the array, even though the GraphQL Resolver will never return null in the array.

Any assistance in this would be greatly appreciated.

All 14 comments

@jdwolk How does Maybe affect this? Could you explain it more with an example? Maybe a reproduction with codesandbox?

It works:
image

But this is not:
image

It is a generated types:

export type FieldsFormQuery = { __typename: 'Query' } & {
  fields: Types.Maybe<
    { __typename: 'FieldsConnection' } & {
      nodes: Array<
        { __typename: 'Field' } & Pick<
          Types.Field,
          'id' | 'handbookId' | 'scope' | 'name' | 'type' | 'required' | 'meta'
        >
      >
    }
  >
}

It is why apollo-codegen generates separate types for each nested property.

Thanks for coming up w/ the example @testarossaaaaa :pray:

So is the answer just "use NonNullable everywhere you need to specify types that might be hidden behind Maybe"? Including deeply nested cases where it's awkward, like in the original example from https://github.com/dotansimha/graphql-code-generator/issues/713#issuecomment-474896208 ?

If so that seems sort disingenuous since, well...it's Maybe so it _might_ actually be null (depending on how you define Maybe - looks like graphql-code-generator defines it in terms of null)

@jdwolk @testarossaaaaa You can use fragments to have seperate types or avoidOptionals: true setting to get rid of Maybe. Let me now if these two options work for you or not?/

edit: Sorry avoidOptionals was not a good solution.

Thanks for the suggestions @ardatan, I'll take a look at them and see if they work

Looking at avoidOptionals, it doesn't solve my problem because the underlying values are still Maybes. Even though TS doesn't treat the values as optionals you still have to "go through" the Maybe somehow to get to the actual type.

Looking into using more fine-grained fragments next.

OK so here's the example I'm working with. I also set preResolveTypes: true in codegen.yml.

Generated types:

Screen Shot 2019-12-06 at 5 25 25 PM

And here's the code:

import React from "react";
import gql from "graphql-tag";
import {
  DogsList_DogFragment,
  DogsList_PersonFragment,
  usePetsIndex_PersonQuery,
} from "../types/graphql";
import { oc } from "ts-optchain";

// This gets pulled out _just_ to make DogsList_DogFragment
const DOGS_LIST__DOG = gql`
  fragment DogsList_Dog on Dog {
    id
    name
    breed {
      id
      name
    }
  }
`;

const DOGS_LIST__PERSON = gql`
  fragment DogsList_Person on Person {
    dogs {
      ...DogsList_Dog
    }
  }
`;

gql`
  query PetsIndex_Person($personId: ID!) {
    person(id: $personId) {
      id
      name
      ...DogsList_Person
    }
  }
`;

///////////////////////////
// interface IDogsListProps {
//   dogs: Array<DogsList_PersonFragment["dogs"][0]>;
// }
// This fails because Maybe doesn't have a [0] property, i.e. the
// structure of the generated type of the "dogs" key on DogsList_PersonFragment is
// Maybe<Array<{ __typename?: "Dog" } & DogsList_DogFragment>>
///////////////////////////

///////////////////////////
// interface IDogsListProps {
//   dogs: Array<NonNullable<DogsList_PersonFragment["dogs"]>[0]>;
// }
// This _works_ but it's not good because:
// 1. It's really ugly, i.e. you need to tell it how to drill down into the
//    thing you care about from the query-level type
// 2. It's even uglier in nested cases
// 3. If you use it everywhere then the type is lying
//   * We're basically telling the type system that we know more than it does,
//     i.e. “this value which might not exist will always be present” when, in fact,
//     it sometimes won't.
///////////////////////////

///////////////////////////
interface IDogsListProps {
  dogs: Array<DogsList_DogFragment>;
}
// This looks nice but it's a hack - we needed to pull out the DogsList_Dog
// fragment for no other reason than to make it "prettier" than ^
///////////////////////////

const DogsList: React.SFC<IDogsListProps> = ({ dogs }) => {
  return (
    <>
      <h2>Dogs</h2>
      {dogs.map((dog, i) => (
        <div key={i}>
          <label>
            <b>Name</b>
          </label>
          <p>{dog.name}</p>
        </div>
      ))}
    </>
  );
};

const PetsIndex: React.SFC = () => {
  const personQuery = usePetsIndex_PersonQuery({
    variables: { personId: "42" },
  });
  const person = oc(personQuery).data.person;

  return (
    <div>
      <h1>Pets</h1>
      <DogsList dogs={person.dogs([])} />
    </div>
  );
};

export default PetsIndex;

This _works_ (i.e. now we only have to deal with the Maybe at the value level, not really at the type level) but it's really non-ideal. I had to create a fragment just to get a type to directly reference dogs without having to "drill down" the types from DogsList_PersonFragment.

Is there anything in the works or some other config option to auto-generate these types without needing a fragment to hack around things?

Currently that's not possible to generate types for each nested field. We used to have this feature but it didn't work well because of the duplicate names etc.

By the way, I'm sorry for misleading about avoidOptionals. There is another option called maybeValue so you can configure the value of Maybe to avoid null. In this case, it can be just;

maybeValue: T 

instead of T | null which is default value.

Don't think that will work in my case unless I'm misunderstanding something. Encoding Maybe like that doesn't really match up w/ reality, ie it's saying you'll always get the underlying T.

I don't want to avoid null in Maybe so much as have a cleaner way to reference the type that graphql-code-generator is wrapping with Maybe. In my example I used ts-optchain to deal w/ Maybe at the value level but I had no way of dealing w/ it at the type level without manually creating that extra fragment

I'm also having this exact same issue, where it's outputting Array<Maybe<Post>>, which means it can be a null object within the array, even though the GraphQL Resolver will never return null in the array.

Any assistance in this would be greatly appreciated.

@ardatan what do you think?

I went thorough this issue again.
I think there are a few workaround for this specific use case.
@JimmyMultani in your case, it depends on how you define your GraphQL schema, because [Something]! might return null inside the array.

If someone want to improve that, feel free to PR.

I do think it makes sense to provide a way to export nested types – each Pick<T, 'field1' | 'field2'> variation of a schema type ideally. That way you don't have to do any tricky typescript magic to reach into nested Maybe types, or mess sacrifice having the most accurate types for your schema (I want null types for nullable parts of the schema!)

I think NonNullable is a workaround, but may lead to a lot of excessive boilerplate. What do you think?

@mshwery I agree, but from our point of view, it will be impossible to maintain, and have a stable output. You can use typescript-compatibility (with strict: true) to get those intermediate types automatically generated.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bastman picture bastman  Â·  3Comments

jagregory picture jagregory  Â·  3Comments

steebchen picture steebchen  Â·  3Comments

edorivai picture edorivai  Â·  3Comments

fvisticot picture fvisticot  Â·  3Comments