この記事は GraphQL Advent Calendar 2020 19日目の記事です。
前回の記事は @policeman-kh さんの GraphQLサーバーをJavaで実装してみる でした。
Apollo ClientのLocal Stateとは
ローカルのデータをApollo Clientに保持しておくことができる仕組みです。GraphAPIからデータを取得する際に,同時にローカルのデータも取得することができるというメリットがあります。また,Reduxなどの状態管理ライブラリの代替的な役割としても使用できます。
やりたいこと
以下のようなDBが既に存在するとします。
今回,users.id
をactiveUserId
としてLocal Stateに保持する方法を考えます。
今回使用した環境
- React
- Apollo Client
- GraphQL Code Generator
- hasura
queryやmutationは既に動く状態で,Local Stateを追加することを想定します。
補足:hasuraとは
PostgreSQLからGraphQL APIサーバーを自動で構築してくれるものです。Postgreのデータベースに格納したデータに対してGraphQLのクエリが発行できるようになります。
codegenの設定
公式ドキュメント
Local Stateを利用する際,GraphQLサーバー側のschemaには存在しないフィールドやカラムをclient-side schemaに自分で追加していく流れになります。なので,まずはcodegenがclient-side schemaを認識するために,codegenの設定ファイルにschemaのパスを追加します。
module.exports = {
schema: [
"http://localhost:8080/v1/graphql",
"src/graphql/local-schema.graphql", //追加
],
client-side schemaの書き方
client-side schemaに以下のように書くことでactiveUserId
というフィールドをqueryに追加することができます。
extend type Query {
activeUserId: Int!
}
これを以下のように使用することができます。
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
の代わりに以下のように書くことで解決しました。
type query_root {
activeUserId: Int!
}
Local Stateの値を操作する
client.writeQueryを利用します。
await client.writeQuery({
query: MyArticlesDocument,
data: { activeUserId: 1 },
});
とすることで,MyArticles
queryのacitveUserId
を1に書き換えることができます。
注意点として,今回は
- query単位でのキャッシュ
- フィールド単位でのキャッシュ
の両方更新されるので,他のqueryでactiveUserId
を使用している場合,そちらのキャッシュも自動的に更新されます。
(キャッシュについてはGraphQL Advent Calendar 2020 2日目で詳しく触れているのでぜひ参考にしてください。)
補足
以前はclient.writeData
(またはcache.writeData
)というメソッドがありました。しかし,公式にある通り,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によると,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を使うのが良さそうです。使ってみたらまた記事に書こうと思っています。