Search code examples
typescriptgraphqlgraphql-codegenurql

How to narrow and reuse TS Types with autogenerated by GraphQL codegen?


I am using graphql codegen together with urql to ensure back-to-back ts safety. But, after generating types and queries, I am having an issue typing the component props when passing data down to the component. Here is an example:

This is the query type generated:

export type AnnotationsQuery = { __typename?: 'Query', threads?: Array<{ __typename?: 'Thread', annotation: { __typename?: 'Annotation', id: string, x: number, y: number, width: number, height: number, key: number, color: string, stroke: number, style: string } }> | null };

Normally, when mapping over threads and passing thread as a prop to the component it could be typed as AnnotationsQuery["threads"][number] but in this case this will not work as the threads?: Array<> is optional, making AnnotationsQuery["threads"][number] not a proper type or any.

Is there a better/correct way to type a single thread even if the threads is optional?


Solution

  • You can use NonNullable

    
    type ThreadOptional = NonNullable<AnnotationsQuery["threads"]>[number] | undefined;
    
    interface Props {
      thread: ThreadOptional;
    }
    
    const ExampleComponent = ({ thread }: Props) => {
      // your component logic here
    };
    

    check it against simple unit test:

    describe("ThreadOptional", () => {
      it("should correctly type a valid thread", () => {
        const thread: ThreadOptional = {
          __typename: "Thread",
          annotation: {
            __typename: "Annotation",
            id: "123",
            x: 0,
            y: 0,
            width: 10,
            height: 10,
            key: 1,
            color: "#000",
            stroke: 1,
            style: "solid"
          }
        };
    
        expect(thread).toBeDefined();
        expect(thread?.annotation.id).toBe("123");
      });
    
      it("should correctly type an undefined thread", () => {
        const thread: ThreadOptional = undefined;
    
        expect(thread).toBeUndefined();
      });
    
      it("should handle null or undefined AnnotationsQuery['threads']", () => {
        const query: AnnotationsQuery = { __typename: "Query", threads: null };
    
        const thread1: ThreadOptional = query.threads?.[0];
        expect(thread1).toBeUndefined();
    
        const thread2: ThreadOptional = undefined;
        expect(thread2).toBeUndefined();
      });
    });