Apolloのキャッシュとリストの更新

## はじめに

小さなLTに挑戦してみたので、その時に調べた事をまとめました。
LTの内容を[ApolloのFragmentの使い方](https://qiita.com/toki_k/items/f9c2258ea6d24247e661)の記事と2つに分割してます。

## 使用技術

* React
* Apollo Client
* GraphQL Code Generator
* Hasura(GraphQLサーバー側。今回はそこまで関係ないかも)

## サンプルアプリ
### 画面イメージ
![スクリーンショット 2021-09-24 13.16.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/2046a11a-ea33-4a44-b875-5ada179974be.png)

### DB
usersテーブル：id,　name
todosテーブル：id,　user_id,　title,　created_at,　updated_at

userがtodoのリストを持っている

### ソースコード

https://github.com/tokio-k/LT-sample-apollo-fragment-and-modify-cache
[ApolloのFragmentの使い方](https://qiita.com/toki_k/items/f9c2258ea6d24247e661)の記事の内容も入っています

## Apolloのキャッシュについて

**クエリの結果をインメモリのキャッシュに自動で保存してくれる**

フラットな構造に正規化してキャッシュに保存してくれます。
idと\_\_typenameをキーとしてキャッシュを管理しています。
users:1のtodosには、「todos:id」のキーのみが__refとして保存されています。

![スクリーンショット 2021-09-24 23.47.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/6d9a5f31-721e-0d6e-b553-181a7c279667.png)
chrome拡張機能 [Apollo Client Devtools](https://chrome.google.com/webstore/detail/apollo-client-devtools/jdkknkkbebbapilgoeccciglkfbmbnfm)で確認しています。


**キャッシュにデータがあればキャッシュから取得する**
デフォルトの挙動では、キャッシュに存在しないデータを取得する時はクエリを発行しますが、キャッシュに存在する場合はキャッシュからデータを取得します。。つまりデータが変更されていても、サーバー側にリクエストせず、キャッシュの古い情報を使ってしまう状況になることもあります。
ここら辺の設定は、[FetchPolicy](https://www.apollographql.com/docs/react/api/core/ApolloClient/#FetchPolicy)で設定できるようです。(勉強したらまたキータにあげようと思います)

**キャッシュが変更された時に、コンポーネントが再レンダリングされる**

キャッシュを利用しているコンポーネントが、キャッシュの変更を検知して再レンダリングを行います。
キャッシュを更新することで、更新後のデータを表示することができます。

## Todoの更新

**mutation updateTodoのレスポンスにidを含んでいる**

![demo-id.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/99dfc3f0-9edb-d3d5-b5f0-bd6d25e1b3f5.gif)

自動でキャッシュが書きかわり、表示も変更されました
idと\_\_typenameで管理しているため、idを返すことでキャッシュに存在するデータと同じであることが認識されます。

キャッシュを書き換えるためには、変更後の値も必要なため、変更したい値も含む必要があります。
(title, created_at, updated_atのキャッシュが書き換わる。created_atは変化しないが。)

**mutation updateTodoのレスポンスにidを含んでいない**

![demo-noid.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/fa85483f-08d6-0d4f-b520-261adb48876f.gif)

idを返さない場合は、表示が更新されませんでした。

## Todoの追加

```.ts
const [addTodo] = useAddTodoMutation()
```
のみの場合

![demo-add-no.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/104967a1-3cbb-441e-2621-56c071a46c95.gif)

追加したTodoは画面に表示されませんでした。
users1のtodosが更新されていません。

Listを更新する方法は２つあります。(subscriptionとかは考えてない)

* refetchQueriesを使って、もう1度サーバー側にリクエストをする
* クライアント側でキャッシュを書き換える (今回はこっちです)

refetchQueriesは簡単に実装できますが、クエリの数が増えます
クライアント側でキャッシュを書き換える場合は、不要なリクエストがなくなりますが実装が少し大変です。

**クライアント側でキャッシュを書き換える**

```.ts
const [addTodo] = useAddTodoMutation({
  update(cache, { data }) {
    cache.modify({
      id: cache.identify({ id: 1, __typename: "users" }), //※1
      fields: {
        todos(existing = []) { // ※2
          const newTodoRef = cache.writeFragment({ //※3
            data: data?.insert_todos_one,
            fragment: TodoFragmentDoc,
          });
          return [...existing, newTodoRef]; //※4
        },
      },
    });
  },
});
```
※1 サンプルのため、userを1で固定しています。実際はuserのidを入れてください。
※2 existingには、todosの中の「\_\_ref: "todos:{id}"」の配列が入っています。
※3 newTodoRefも「\_\_ref: "todos:{id}"」の形式です。
※4 それらを1つの配列としてreturnしています。

![demo-add.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/54c51087-17da-a80a-7457-705e23b5a43c.gif)
無地に追加したTodoを表示することができました。




## まとめ

* Apolloのキャッシュは、正規化した構造で、idと\_\_typenameをキーとして、管理している。
* キャッシュを更新することで表示を変えられる


## おまけ

削除も更新と同じ要領でキャッシュを編集します。こんな感じです。サンプルコードにはないです。

```.ts
cache.modify({
  id: cache.identify({ id: 1, __typename: "users" }),
  fields: {
    todos(existingTodoRefs, { readField }) {
      return existingTodoRefs.filter((todoRef: any) => {
        return todo.id !== readField("id", todoRef);
      }
    },
  },
});
```
キャッシュの中から、削除したいTodoと同じidを持つTodoを探して消しているだけです

## 参考記事

https://www.apollographql.com/docs/react/caching/overview/
https://www.apollographql.com/docs/react/caching/cache-interaction/
https://www.apollographql.com/docs/react/caching/cache-configuration/
https://zenn.dev/kazu777/articles/b64935ea7d6fee
https://caddi.tech/archives/2195
https://tech.wasd-inc.com/entry/2020/12/02/222922