今更ながら graphql-code-generator の便利さを痛感する その２

# はじめに

今日も今日とて、フロントReact + バックRailsの...

ということで、今回は[今更ながら graphql-code-generator の便利さを痛感する](https://qiita.com/pure-adachi/items/f1e03941e8c0ac47c7ee)の続きとして、登録、削除等の`Mutation`の部分をやっていきます。

※構成は[前回](https://qiita.com/pure-adachi/items/f1e03941e8c0ac47c7ee#%E4%BB%8A%E5%9B%9E%E3%81%AE%E6%A7%8B%E6%88%90)と同じです。

# とりあえずApolloの公式に沿ってやってみる

まずはTODOを追加する仕組みを作っていきます。

追加用のテキストフィールドを用意しました。
ここに値を入力して、エンターで追加処理が実行される仕組みとします。

![スクリーンショット 2020-09-22 12.44.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/e5d5fff4-1a89-6748-f1fa-36ae773ae8c4.png)

```diff:src/App.tsx
+import { gql, useMutation } from "@apollo/client";

...

+const ADD_TODO = gql`
+  mutation addTodo($name: String!) {
+    addTodo(input: { name: $name }) {
+      todo {
+        id
+        name
+      }
+    }
+  }
+`;

 const App = () => {
   const { loading, data } = useTodosQuery();
+  const [addTodo] = useMutation(ADD_TODO);

...
         </p>
+        <input
+          type="text"
+          onKeyPress={(e) => {
+            if (e.key === "Enter") {
+              addTodo({ variables: { name: e.currentTarget.value } });
+            }
+          }}
+        />
         {loading ? (
...
```

文字を入力して、エンターで追加処理が実行できる仕組みができました。

エンター押下後、画面をリロードすると、TODOが追加されていることがわかります。

![スクリーンショット 2020-09-22 14.18.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/0d7235de-e1d1-e1cd-6fab-6e6707efdd68.png)

リロードしないと動きがわからないので、ついでに、追加処理に加え、入力欄のクリア、一覧情報の再取得処理をやっておきます。

```diff:src/App.tsx
...
-  const { loading, data } = useTodosQuery();
+  const { loading, data, refetch } = useTodosQuery();
-  const [addTodo] = useMutation(ADD_TODO);
+  const [addTodo] = useMutation(ADD_TODO, {
+    update() {
+      refetch();
+    },
+  });

...

           onKeyPress={(e) => {
             if (e.key === "Enter") {
               addTodo({ variables: { name: e.currentTarget.value } });
+              e.currentTarget.value = "";
             }
           }}
```

特に型関連でエラーは起きませんでした。

# もう少し便利になるよう作り込んでみる

何も入力せずに、登録処理を実行し、エラーが出るような仕組みを入れます。

`Todo`にバリデーションエラーのフィールドを追加して、以下のような値がレスポンスに含まれるようにしました。

```json
"errors":[{"field":"name","error":"blank"}]
```

また、処理の結果が正常に行われたかの`result`フィールド（Boolean）を追加しました。

`result`が`true`なら一覧をリロード、`false`ならエラー情報を`alert`で表示する仕組みを作ってみます。

```diff:src/App.tsx
 const ADD_TODO = gql`
   mutation addTodo($name: String!) {
     addTodo(input: { name: $name }) {
       todo {
         id
         name
+        errors {
+          field
+          error
+        }
      }
+      result
    }
  }
`;
```

```diff:src/App.tsx
   const [addTodo] = useMutation(ADD_TODO, {
-    update() {
-      refetch();
-    },
-  });
+    update(
+      _cache,
+      {
+        data: {
+          addTodo: {
+            todo: { errors },
+            result,
+          },
+        },
+      }
+    ) {
+      if (result) {
+        refetch();
+      } else {
+        errors.forEach(({ field, error }) => {
+          alert(`${field} ${error}`);
+        });
+      }
+    },
+  });
```

`errors`の型が宣言されていないので、エラーが発生しました。

![スクリーンショット 2020-09-22 16.10.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/60237b9a-7b8d-11cb-994a-61b234f9b089.png)

型宣言をしてあげます。

```tsx
type ValidationErrorType = {
  field: string;
  error: string;
};
```

```diff
-errors.forEach(({ field, error }) => {
+errors.forEach(({ field, error }: ValidationErrorType) => {
  alert(`${field} ${error}`);
});
```

エラーは解消され、バリデーションエラーメッセージが`alert`で確認できるようになりました。

![スクリーンショット 2020-09-22 16.15.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/f54db206-1ce5-2fcb-4a12-1c70d1e8a062.png)


前回同様、`graphql-code-generator`を使って、綺麗にしていこうと思います。

# graphql-code-generatorを使う

まずは設定から

`queries`とは別に`mutations`用のディレクトリを作成して、今回作成したクエリは、そちらに格納します。

```diff:codegen.yml
-documents: ./graphql/queries/*.graphql
+documents:
+  - ./graphql/mutations/*.graphql
+  - ./graphql/queries/*.graphql
```

```graphql:graphql/mutations/add_todo.graphql
mutation addTodo($name: String!) {
  addTodo(input: { name: $name }) {
    todo {
      id
      name
      errors {
        field
        error
      }
    }
    result
  }
}
```

これで`yarn generate`を実行すると、`src/types.d.ts`にTODOを追加する用の`useAddTodoMutation`が用意されました。

`useAddTodoMutation`を使って追加処理を書き直してみます。

```diff
-import { useTodosQuery } from "./types.d";
+import { useTodosQuery, useAddTodoMutation } from "./types.d";

+const [addTodo] = useMutation(ADD_TODO, {
+const [addTodo] = useAddTodoMutation({
```

ん？エラーが起きました。

![スクリーンショット 2020-09-22 21.01.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/8208820d-ab81-3252-3374-0c49dd6fa5ec.png)

どうやら`data`は`null`か`undefined`の可能性があるそうです。

それらを考慮して、少し書き直します。

```tsx
const [addTodo] = useAddTodoMutation({
  update(_cache, { data }) {
    const result = data?.addTodo?.result || false;
    const errors = data?.addTodo?.todo.errors || [];

    if (result) {
      refetch();
    } else {
      errors.forEach((e) => {
        if (e) alert(`${e.field} ${e.error}`);
      });
    }
  },
});
```

少し手直しが必要でしたが、クエリと、型宣言の用意が不要になったので、ソース的にはスッキリしました :thumbsup: 

# 同様に削除処理を実装してみる

雑ですが、各TODOの隣に、削除ボタンを用意して、削除処理を実行できるようにします。

![スクリーンショット 2020-09-27 23.50.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/6c134452-84eb-415f-3636-0d5ec00ebdd6.png)


```graphql:graphql/mutations/del_todo.graphql
mutation delTodo($id: ID!) {
  delTodo(input: { id: $id }) {
    todo {
      id
    }
  }
}
```

削除対象のTODOを特定するために、IDを取得する必要があります。

`graphql/queries/todos.graphql`では、TODOの`name`だけ取得しているので、`id`もとるように修正します。

```diff:graphql/queries/todos.graphql
 query todos {
   todos {
+    id
     name
   }
 }
```

`yarn generate`を実行すると`useDelTodoMutation`関数が`src/types.d.ts`に生まれました。

この`useDelTodoMutation`を使って、`削除`ボタンクリック時に削除処理が実行できるようにします。

```diff:src/App.tsx
-import { useTodosQuery, useAddTodoMutation } from "./types.d";
+import { useTodosQuery, useAddTodoMutation, useDelTodoMutation } from "./types.d";


+const [delTodo] = useDelTodoMutation({
+  update() {
+    refetch();
+  }
+});


-{data && data.todos.map(({ name }, i) => <li key={i}>{name}</li>)}
+{data && data.todos.map(({ id, name }, i) => <li key={i}>{name}<button onClick={() => delTodo({ variables: { id } })}>削除</button></li>)}
```

お手軽に削除処理が実装できました :tada:

![add-del.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/5b6a3b3c-a2c8-bc2a-fef2-16fddeecb176.gif)

# 最後に

一つの画面、コンポーネントで、一覧取得、追加処理、削除処理を実装したので、少しボリュームの大きめなファイルとなってしまったかもしれません。

別のファイルに型定義やクエリ情報を定義して、`import`でも良いかもしれないですが、人力で且つ、複数人となると、管理が次第に煩雑になったりする可能性があると思います。

これが、ある意味`graphql-code-generator`のルールに則って開発をしていると、その問題が解消されるのではないかと思いました。

今回は、かなり小規模な例として、実装したので、導入にあたって障害となる箇所をあまり感じられませんでした。
LGTM なツールと思います :thumbsup: