Apollo Client × codegen でのLocalStateの使い方

この記事は [GraphQL Advent Calendar 2020](https://qiita.com/advent-calendar/2020/graphql) 19日目の記事です。
前回の記事は @policeman-kh さんの [GraphQLサーバーをJavaで実装してみる](https://qiita.com/policeman-kh/items/2714e4c138353041ce25) でした。


# Apollo ClientのLocal Stateとは

[公式ドキュメント](https://www.apollographql.com/docs/react/local-state/local-state-management/#gatsby-focus-wrapper)

ローカルのデータをApollo Clientに保持しておくことができる仕組みです。GraphAPIからデータを取得する際に，同時にローカルのデータも取得することができるというメリットがあります。また，Reduxなどの状態管理ライブラリの代替的な役割としても使用できます。



# やりたいこと

以下のようなDBが既に存在するとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/479246/e3d70f6b-1795-801b-78c4-61521880478c.png)

今回，`users.id`を`activeUserId`としてLocal Stateに保持する方法を考えます。


# 今回使用した環境

- React
- Apollo Client
- GraphQL Code Generator
- [hasura](https://hasura.io/)

queryやmutationは既に動く状態で，Local Stateを追加することを想定します。


##### 補足：hasuraとは
PostgreSQLからGraphQL APIサーバーを自動で構築してくれるものです。Postgreのデータベースに格納したデータに対してGraphQLのクエリが発行できるようになります。




# codegenの設定
[公式ドキュメント](https://graphql-code-generator.com/docs/integrations/apollo-local-state)
Local Stateを利用する際，GraphQLサーバー側のschemaには存在しないフィールドやカラムをclient-side schemaに自分で追加していく流れになります。なので，まずはcodegenがclient-side schemaを認識するために，codegenの設定ファイルにschemaのパスを追加します。


```codegen.js
module.exports = {
  schema: [
      "http://localhost:8080/v1/graphql",
      "src/graphql/local-schema.graphql", //追加
  ],
```

https://github.com/marina-ki/apollo-client_local-state/blob/main/packages/web/codegen.js


# client-side schemaの書き方

client-side schemaに以下のように書くことで`activeUserId`というフィールドをqueryに追加することができます。

```src/graphql/local-schema.graphql
extend type Query {
  activeUserId: Int!
}
```

これを以下のように使用することができます。


```src/graphql/queries/MyArticles.graphql
query MyArticles($id: Int!) {
  activeUserId @client @export(as: "id")
  user: users_by_pk(id: $id) {
    id
    name
    articles {
      id
      body
    }
  }
}
```

`@client`の箇所は，「キャッシュされているactiveUserIdを使用する」ということです。
`@export(as: "id")`によって，`activeUserId`を`$id`としてusers_by_pkの引数に渡すことができます。

その結果，useQueryを使うときにわざわざ`activeUserId`を渡す必要がなくなります。

```
const { data } = useMyArticlesQuery();
```

また，

```
console.log(data?.activeUserId);
```

という形でキャッシュされたactiveUserIdを取得することもできます。


### ※hasuraでの注意点
hasuraでは上記の`local-schema.graphql`の書き方ではエラーが出ます。

```
 AggregateError: 
        GraphQLDocumentError: Cannot query field "activeUserId" on type "quer
y_root".
```

`extend type Query`の代わりに以下のように書くことで解決しました。

```src/graphql/local-schema.graphql
type query_root {
  activeUserId: Int!
}
```



# Local Stateの値を操作する
[client.writeQuery](https://www.apollographql.com/docs/react/caching/cache-interaction/#writequery-and-writefragment)を利用します。


```
    await client.writeQuery({
      query: MyArticlesDocument,
      data: { activeUserId: 1 },
    });
```

とすることで，`MyArticles` queryの`acitveUserId`を1に書き換えることができます。


注意点として，今回は

- query単位でのキャッシュ
- フィールド単位でのキャッシュ

の両方更新されるので，他のqueryで`activeUserId`を使用している場合，そちらのキャッシュも自動的に更新されます。

(キャッシュについては[GraphQL Advent Calendar 2020 2日目](https://tech.wasd-inc.com/entry/2020/12/02/222922)で詳しく触れているのでぜひ参考にしてください。)



### 補足

以前は`client.writeData`(または`cache.writeData`)というメソッドがありました。しかし，[公式](https://www.apollographql.com/docs/react/migrating/apollo-client-3-migration/#cache-improvements)にある通り，v3.0で廃止されています。

> client|cache.writeData have been fully removed. client|cache.writeQuery, client|cache.writeFragment, and/or cache.modify can be used to update the cache.

[PR](https://github.com/apollographql/apollo-client/pull/5923)によると，`cache.writeData`を呼ぶと全てのqueryを実行し直すため，非効率的だったそうです。




# まとめ

Reduxなどを使わずに，Apolloだけでremoteのデータもlocalのデータも一元管理することを目的としてLocal Stateが導入されたそうです。
(参考：https://www.apollographql.com/blog/the-future-of-state-management-dd410864cae2/)

> Historically, Apollo users managed that 20% in a separate Redux or MobX store. This was a doable solution for Apollo Client 1.0, but when Apollo Client 2.0 migrated away from Redux, syncing local and remote data between two stores became trickier. We often heard from our users that they wanted to encapsulate all of their application’s state inside Apollo Client and maintain one source of truth.

今回は「`activeUserId`をLocalStateにいれることでqueryを簡単に使用できるようにする」という目的で使ってみましたが，もし単に状態管理をしたいだけであれば，Apollo Client v3.0での新機能の[reactive variables](https://www.apollographql.com/docs/react/local-state/reactive-variables/)を使うのが良さそうです。使ってみたらまた記事に書こうと思っています。