Apollo Client ドキュメントメモ

# Apollo
* GraphqlのJavaScriptクライアント https://www.apollographql.com/docs/react/data/queries/
* この記事はReactでの使い方メモです
* 順番的にはGraphQLのドキュメントが先な気がします。　https://graphql.org/learn/

## gql
* graphqlはJavaScriptっぽいけど全く別のクエリ言語
* gqlというgraphqlのパーサーでパースしたものを使用する

```graphql
const GET_DOGS = gql`
  {
    dogs {
      id
      breed
    }
  }
`;
```

## useQuery
+ loading, error, dataが変わったら再レンダリングを行うReact Hook
+ `const { loading, error, data, refetch, networkStatus } = useQuery(GET_DOGS)`こんな感じで使う
+ 第二引数にオプションを渡す。variablesもその1つ `useQuery(GET_DOGS, { variables: { json: "aaa" } })`
+ キャッシュは自動で有効になっていて、クエリA => クエリB => クエリAをすると、2回目は高速に表示される。サーバーと同期を取りたい時はpollingとrefetchingを行う必要がある
+ Apollo内ではgraphQLの`__typename`と`id`で同一のオブジェクトかどうか判定している

## Polling
* 初回ロードした後キャッシュを使用し続けるので、最新の情報をとってくる必要がある。方法はPollingとRefetchingがある
* 一定の間隔でクエリを投げ続ける
* pollIntervalオプションで頻度を設定できる
```grapnel
const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
    variables: { breed },
    pollInterval: 500, 0.5秒ごとにクエリを発行する
  });
```
* `startPolling` `stopPolling`funcで、動的に開始したり終了したりできる。useQueryの戻り値の一つ

## Refetching
* なんらかのイベントでクエリを再度投げる

```javascript
const { loading, error, data, refetch } = useQuery(GET_DOG_PHOTO, {
    variables: { breed }
  });

<button onClick={() => refetch()}>Refetch!</button>
```
* variablesを与えることもできる。与えなければ、前回と同じvariablesが使われる
* もし一部のvariablesだけrefetchで更新した場合は、残りのvariablesは初回と同じものが使われる

```graphql
<button onClick={() => refetch({
  breed: 'dalmatian'
})}>Refetch!</button>
```
* refetchを呼んだ時、デフォルトだとloadingはずっとfalseのまま。変わらないのでレンダリングもされない。
* `notifyOnNetworkStatusChange: true`を渡すと、refetch中にloadingがfalseにすることができる。
* ただ、これだと初回ロード中なのかrefetchロード中なのかわからないので、`networkStatus`で判定する

```javascript
const { loading, error, data, refetch, networkStatus } = useQuery(

if (networkStatus === NetworkStatus.refetch) return 'Refetching!';
  if (loading) return null; ローディングは下に書かないとreturnしてしまう
```
* networkStatusは、8つの状況を返す。https://github.com/apollographql/apollo-client/blob/main/src/core/networkStatus.ts

## Inspecting error states
* エラーの時にどういう挙動をするかを、`errorPolicy`オプションで指定できる。
* デフォルトは`none`で、エラーが起きたらレスポンスを破棄して何もせず、戻り値の`error`プロパティを`truethy`にするだけ。エラークラスが配列で入ってくる
* `all`にすると、レスポンスを破棄せずそのレスポンスを返す
* 他の処理方法についてはこれだけでセクションになっている [Handling operation errors - Client (React) - Apollo GraphQL Docs](https://www.apollographql.com/docs/react/data/error-handling/)

##  useLazyQuery
* useQueryはそのコンポーネントがレンダリングされた時に自動で実行されるが、`useLazyQuery`はなんらかのイベントでクエリを実行する
* `const [getDog, { loading, error, data }] = useLazyQuery(GET_DOG_PHOTO);`
* ボタン押したとか任意のタイミングで`getDog({options})`を呼ぶとクエリが実行される

## fetch policy
* `useQuery`を呼んだ時に、まずは利用可能なキャッシュが無いか確認し、なければクエリを実行する。この挙動は`fetch policyで設定できる。`
* デフォルトは`cache-first`。その名の通り。
* 欲しいデータのうち1つでも足りないのがあればクエリを叩く
* `cache-and-network`ポリシーでは、キャッシュとクエリ両方からデータを要求する。まずはキャッシュを返しておき、後から取得したクエリの結果によってキャッシュが更新されたら、useQueryの結果自体も更新する。キャッシュ優先だけどもしサーバーのデータと異なってたらちゃんと更新しておきたい時に便利。

```javascript
const { loading, error, data } = useQuery(GET_DOGS, {
  fetchPolicy: "network-only" // キャッシュを確認せず毎回クエリを実行
});
```
* `fetchPolicy`と`nextFetchPolicy`両方設定することで、1回目は`fetchPolicy`の設定に従い、2回目以降は`nextFetchPolicy`に従う

# useMutation
* データを更新するmutatoinクエリを投げるために使う

```javascript
import { gql, useMutation } from '@apollo/client';

const INCREMENT_COUNTER = gql`
  mutation IncrementCounter {
    currentValue
  }
`;

function MyComponent() {
  const [mutateFunction, { data, loading, error }] = useMutation(INCREMENT_COUNTER);
}
```
* mutationFunctionを任意のタイミングで呼べば、mutationが実行される
* useQueryと違い、レンダリング時には自動でクエリが実行されない
* data, loading, errorなどはuseQueryと似ている
* variablesの渡し方2つ
    * `useMutation`の第二引数に渡す `useMutation(QUERY, { variables: { text: "hoge" } })` これはデフォルトのvariablesとして振る舞う
    * `mutationFunction`の引数に渡す。`mutaionFunction({ variables: { text: "hoge" } })`。デフォルトを上書きする
* mutationした後、ローカルのキャッシュも更新するためにrefetchしたい。mutationのレスポンスでキャッシュを更新することは可能だが、複雑になりがちなので最初は単にrefetchを実行するのがおすすめとのこと。

## Refetching Queries with Mutation
* useMutationの`refetchQueris`オプションに配列でgqlによってパースされたクエリか、クエリ名を与えると実行される
* variablesは直近実行された時のvariablesがそのまま入る

```javascript
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
  refetchQueries: [
    GET_POST, // DocumentNode object parsed with gql
    'GetComments' // Query name
  ],
});
```

## Updating the cache directly
* mutationのクエリの結果には大抵update後のオブジェクトが入っている。
* オブジェクトの情報が十分であれば、キャッシュが更新される。例えばUserの情報を更新後、Userの情報を取得したいみたいな時。
* しかし、mutationのクエリの戻り値に十分な情報がない、または関連する別のオブジェクトのキャッシュも更新しないといけない時には`update function`を使う必要がある　https://www.apollographql.com/docs/react/data/mutations/#the-update-function

## キャッシュについて深堀
https://www.apollographql.com/blog/apollo-client/caching/demystifying-cache-normalization/

* QueryかMutationが実行されるたびに、Apollo内部で正規化(normalization)が行われている。そのプロセスは、分解 => ID生成 => 保存(マージ + 上書き)である。
* レスポンスを解析し、id と __typenameを繋げ、データを一意に特定できるIDを作成する。例：`TodoType:1`
* もしフィールドにidが存在しない場合には、データを特定できる他のフィールドを[Key fields](https://www.apollographql.com/docs/react/caching/cache-configuration/#customizing-identifier-generation-by-type)で指定する必要がある。
* このIDによって扱うデータを特定し、キャッシュしたり上書きしたりする
* レスポンスがネストしていたり配列だったとしても、TypeとIDによって一意に区別できる単位に分解し、フラットにした形でキャッシュする
* また、実行したクエリ自体の内容と、その時の変数、結果もキャッシュしているため、配列で取得した結果の順番も保持している。クエリの保存の際には、実データではなくType + IDで特定できる個々のデータへの参照だけを保持している
* キャッシュはJSONにシリアライズ可能。`cache.extract()`でその時点のキャッシュを取り出し、`cache.restore(snapshot)`で再キャッシュすることもできる
* キャッシュを更新する際、マージしながら上書きする。新しいフィールドが存在すれば追加し、重複していたら最新のデータに更新する

#### 自動でキャッシュが更新されるケース
* Query。単にデータを取得する
* 1つのType+IDのアイテムをMutationで更新し、レスポンスとしてそのアイテムの新しいデータが返ってくる場合
* 複数のアイテムをMutationで更新し、レスポンスとして更新されたアイテムの配列を受け取る場合。

#### 自動でキャッシュが更新されないケース
* これらはupdate functionで手動でキャッシュをmodifyするか、再度Queryを実行することでキャッシュを更新する必要がある。
* レスポンスのデータと関係のないキャッシュを更新したいケース(あたりまえ)。副作用(side-effects)を想定していて、例えばログアウトのMutationをした時に、ユーザーに紐づくキャッシュを消したい(更新したい)ケース。ログアウト自体のレスポンスは、`success`みたいな感じだからキャッシュのデータと関係ない
* 複数のアイテムをMutationで更新するときに、レスポンスのフィールドが欠けている場合
* アイテムを生成する場合。例えばユーザー生成時に、ユーザー一覧に追加したいが、Apolloは新Itemを一覧に追加できるのか判定できないため。
* アイテムの削除。アイテムは消えても、既存の一覧などから消していいかどうかはApolloは判断がつかないため。

#### FieldPolicy
* Root Query Field Policy
* サーバーのgraphqlスキーマに存在しないクエリを投げると、キャッシュ等から任意のデータを取り出して返すことができる

```javascript
const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        readManagers: { // readManagersというクエリを作成してしまえる
          read(managers, { readField }) {
            const employees = readField('employees');
            // Output:
            // {
            //     __typename: 'EmployeesResponse',
            //     data: [
            //         { __ref: 'Employee:1' },
            //         { __ref: 'Employee:2' },
            //     ]
            // }
            return (employees?.data ?? []).filter(employeeRef => {
              const employeeRole = readField('role', employeeRef);
              return employeeRole === 'Manager'; // roleがManagerなemployeeを配列で返す
            });
          }
        }
      },
    },
  },
});

```