ペペロミア開発ブログ

React Nativeで開発しているアプリ「ペペロミア」の技術系記事を投稿してます

graphql-codegen/typescript-react-apolloでHooks部分まで自動生成する

Firestoreの新設計に作り直しの作業が終わらないので、一旦途中記事を作成

https://github.com/wheatandcat/Peperomia/pull/709/files

新設計に合わせてアプリ側もGraphQLで再実装中です。

graphql-codegenとは

前に別記事で触れましたが、graphql-codegenを導入するとgraphqlのスキーマから自動でtypeファイルを生成してくれます

www.wheatandcat.me

webでの実装は、ここまでで終了していましたが、ReactではHooks部分まで自動生成できるみたいなので実装してみました。

graphql-codegen/typescript-react-apolloとは

graphql-code-generator.com

graphql-codegen/typescript-react-apolloを導入することで、Hooks、Component、HOCなどを自動生成することが可能になります。 ペペロミアでは、ほぼ全てReact Hooksに移行が完了しているので、Hooksの部分を自動生成して使用していきます。

実装

まず、graphql-codegen/typescript-react-apolloのインストール

$ yarn add @graphql-codegen/typescript-react-apollo

gqlファイルを作成します。

■ src/queries/calendar.gql

query Calendar($date: String!) {
  calendar(date: $date) {
    id
    date
    item {
      id
      title
      kind
      itemDetails {
        id
        title
        kind
        memo
        url
        place
        priority
      }
    }
  }
}

graphql-codegenの設定ファイルを追加

■ codegen.yml

overwrite: true
schema:
  - ./schema.graphqls
documents:
  - "./src/queries/**/*.gql"
generates:
  ./src/queries/api/index.ts:
    hooks:
      afterOneFileWrite:
        - yarn codegen:lint:fix
    plugins:
      - typescript
      - typescript-operations
      - typescript-react-apollo
    config:
      skipTypename: false
      withHooks: true       
      withHOC: false
      withComponent: false
      apolloReactHooksImportFrom: '@apollo/client'

withHooksをtrueに設定しています

      withHooks: true       

最後にcodegenを実行するscriptを追加

■ package.json

"scripts": {
    ...(略)

    "download:schema.graphqls": "curl -L -O https://raw.githubusercontent.com/wheatandcat/PeperomiaBackend/master/graph/schema.graphqls",
    "codegen": "npm run download:schema.graphqls && graphql-codegen",
    "codegen:lint:fix": "eslint --fix ./src/queries/api/index.ts"

これで自動生成までの実装は完了、以下のコマンドを実行する

$ yarn codegen

以下のファイルが自動生成されます。

■ src/queries/api/index.ts

import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
import * as ApolloReactHooks from '@apollo/client';

...略)

export type Calendar = {
  __typename?: 'Calendar';
  id: Scalars['ID'];
  /** 日付 */
  date: Scalars['String'];
  /** true: パブリック、false: プライベート */
  public: Scalars['Boolean'];
  /** スケジュール */
  item: Item;
};

...略)

export type CalendarQueryVariables = Exact<{
  date: Scalars['String'];
}>;

export type CalendarQuery = { __typename?: 'Query' } & {
  calendar?: Maybe<
    { __typename?: 'Calendar' } & Pick<Calendar, 'id' | 'date'> & {
        item: { __typename?: 'Item' } & Pick<Item, 'id' | 'title' | 'kind'> & {
            itemDetails?: Maybe<
              Array<
                Maybe<
                  { __typename?: 'ItemDetail' } & Pick<
                    ItemDetail,
                    | 'id'
                    | 'title'
                    | 'kind'
                    | 'memo'
                    | 'url'
                    | 'place'
                    | 'priority'
                  >
                >
              >
            >;
          };
      }
  >;
};

...略)

/**
 * __useCalendarQuery__
 *
 * To run a query within a React component, call `useCalendarQuery` and pass it any options that fit your needs.
 * When your component renders, `useCalendarQuery` returns an object from Apollo Client that contains loading, error, and data properties
 * you can use to render your UI.
 *
 * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
 *
 * @example
 * const { data, loading, error } = useCalendarQuery({
 *   variables: {
 *      date: // value for 'date'
 *   },
 * });
 */
export function useCalendarQuery(
  baseOptions?: ApolloReactHooks.QueryHookOptions<
    CalendarQuery,
    CalendarQueryVariables
  >
) {
  return ApolloReactHooks.useQuery<CalendarQuery, CalendarQueryVariables>(
    CalendarDocument,
    baseOptions
  );
}
export function useCalendarLazyQuery(
  baseOptions?: ApolloReactHooks.LazyQueryHookOptions<
    CalendarQuery,
    CalendarQueryVariables
  >
) {
  return ApolloReactHooks.useLazyQuery<CalendarQuery, CalendarQueryVariables>(
    CalendarDocument,
    baseOptions
  );
}
export type CalendarQueryHookResult = ReturnType<typeof useCalendarQuery>;
export type CalendarLazyQueryHookResult = ReturnType<
  typeof useCalendarLazyQuery
>;
export type CalendarQueryResult = Apollo.QueryResult<
  CalendarQuery,
  CalendarQueryVariables
>;
export const CalendarsDocument = gql`
  query Calendars($startDate: String!, $endDate: String!) {
    calendars(startDate: $startDate, endDate: $endDate) {
      id
      date
      item {
        id
        kind
      }
    }
  }
`;

...略)

と、CalendarのtypesとHooksファイルが生成されます。

●自動生成されたHooks

export function useCalendarQuery(
  baseOptions?: ApolloReactHooks.QueryHookOptions<
    CalendarQuery,
    CalendarQueryVariables
  >
) {
  return ApolloReactHooks.useQuery<CalendarQuery, CalendarQueryVariables>(
    CalendarDocument,
    baseOptions
  );
}

上記のHooksをComponentで使用すると、以下みたいな感じになります。

■ src/components/pages/Calendar/Connected.tsx

import React, { memo, useCallback } from 'react';
import { useCalendarQuery } from 'queries/api/index';
import { Props as IndexProps } from './';
import Plain, { QueryProps } from './Plain';

export type CalendarType = QueryData<QueryProps, 'calendar'>;
export type ItemDetailType = ArrayType<CalendarType['item']['itemDetails']>;

type Props = IndexProps & {
  date: string;
};

export type ConnectedType = {
  onDismiss: () => void;
};

const Connected: React.FC<Props> = memo((props) => {
  const { data, loading, error } = useCalendarQuery({
    variables: {
      date: props.date,
    },
  });

  return (
    <Plain data={data} loading={loading} error={error} />
  );
});

export default Connected;

実装的には、useCalendarQueryをimportして

import { useCalendarQuery } from 'queries/api/index';

variablesを設定してあげれば、そのまま値の取得が可能です。

const Connected: React.FC<Props> = memo((props) => {
  const { data, loading, error } = useCalendarQuery({
    variables: {
      date: props.date,
    },
  });

データの再取得したい場合は、variablesを変えるかrefetchを呼べばOKです。

と、こんな感じでgraphql-codegen/typescript-react-apolloを使えばAPI呼び出し部分まで自動生成できるようになるので、 かなり便利です。

これを使用して新設計への移行を進めようと思います