LoginSignup
8
6

More than 3 years have passed since last update.

apollo-clientを使っていてデータが変に重複してる(キャッシュが変)?と思った時に見てみる事

Last updated at Posted at 2019-09-02

例えばこういう感じのidが重複するパターンのデータをapolloを使って受け取った場合、

{
  "data": {
    "results": [
      {
        "id": 1,
        "className": "ClassA",
        "value": 42
      },
      {
        "id": 1,
        "className": "ClassB",
        "value": 24
      }

    ]
  }
}

通常通りこういう感じで表示すると

const client = useApolloClient()
client.query({
  query: QUERY,
})
.then(({ data }) => {
  console.log(JSON.stringify(data, null, 2))
})

こんな感じになってしまう。(ClassAがClassBのデータに上書きされてる

{
  "results": [
    {
      "id": 1,
      "className": "ClassB",
      "value": 24
    },
    {
      "id": 1,
      "className": "ClassB",
      "value": 24
    }
  ]
}

原因

ApolloClientを作る時に特段事情がなければInMemoryCacheをこういう風にデフォルトのままで使ってると思う。

import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { ApolloClient } from 'apollo-client';

const cache = new InMemoryCache();

const client = new ApolloClient({
  link: new HttpLink(),
  cache
});

このままだと、GraphQLで返ってくるデータにid(もしくは_id)があると、これをキーとしてキャッシュしてしまう。この為、1つの問い合わせの中に複数テーブルのデータが混ざる場合、idが重複してしまう可能性があり、これによりGraphQL自体は正しいが(Networkタブでは期待通りのデータが返ってきてる)、ApolloClientで取れるデータが期待通りにならない。

https://www.apollographql.com/docs/react/advanced/caching/#normalization
By default, InMemoryCache will attempt to use the commonly found primary keys of id and __id for the unique identifier if they exist along with __typename on an object.

解決方法

  1. uuidを使う
  2. ulidを使う
  3. InMemoryCachenormalizationをする
  4. idを架空のランダムな物にして(例えばその場でuuidをつくって使う)、_idに本来入れるプリマリーキーを入れる

1の場合、グローバルにユニークになるのでこのcachingと相性が良くなるが、新規プロジェクトじゃないとかなりやっかい。その上uuidは基本ソート出来ないのでRailsなどで使うModel.firstみたいな物が期待した物にはならない。(idが登録順序にならないので)

2の場合、cachingとの相性、ソート可と便利だが、やはり新規プロジェクトじゃないとかなりやっかい。

3の場合、面倒。上の例だとApolloClientを作る際にこのような形でInMemoryCacheを設定する必要がある。似たようなクエリーがあればその数ほどやらないといけない。

import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory';

...
...

const cache = new InMemoryCache({
  dataIdFromObject: object => {
    switch (object.__typename) {
      case 'Result': return `${object.className}-${object.id}`;
      default: return defaultDataIdFromObject(object);
    }
}});

...

4の場合、React側でidは使い物にならないが、とりあえず期待と違うキャッシングはされない。React側ではidの代わりに_idを使う(_idにこだわる必要は無く名前は何でも良いが...)。

query {
  results{
    id
    _id
    className
    value
  }
}
# 例えばRuby側がサーバーで、データを返すとして
def ...
  class_a = ClassA.find(id)

  results = [
    {
      id: SecureRandom.uuid, # 例えばuuidを使う
      _id: class_a.id, # React側ではこちらを使う
      ...
      ...
    }
    ...
  ]

  { results: results }
end

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6