ESM アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

Apolloクライアントで同じクエリが複数回呼ばれる問題について

宮崎からお送りします。yoshinoです。

私が参加するプロジェクトでは、フロントエンドにNext.jsを採用していて、バックエンドサーバへのリクエストの際には、ApolloクライアントとGraphQLを利用しています。 先日、同じページから、同一のクエリを複数回リクエストする問題が発生していて、修正する機会がありましたので、その時のことを書こうと思います。

問題

QUERY_Aです。

const QUERY_A = graphql(`
    query query_a {
        user {
            profile {
               name
               email
            } 
            address {
              id
              prefecture
            }  
        }
   }
`)

特別なことをするわけでもなく、普通にデータをフェッチして使います。

const { data, loading, error } = useQuery(QUERY_A)

Chromeのデベロッパーツールを利用してNetworkを見ると、QUERY_Aよりも前にQUERY_Bが呼ばれていることもわかりました。

const QUERY_B = graphql(`
    query query_b {
        user {
            id
            isAuthenticated
        }
    }
`)

QUERY_B -> QUERY_A -> QUERY_B -> QUERY_A -> QUERY_B のように、QUERY_Bが3回、QUERY_Aが2回呼ばれていました。

解決方法

今回のケースでは、idがない箇所にidを足すことで、複数回クエリする問題は解決しました。

const QUERY_A = graphql(`
    user {
+       id
        profile {
+          id
           name
           email
        } 
        address {
          id
          prefecture
        }  
    }
`)

考えられる理由

useQueryのfetch-policyのデフォルト値はcache-firstになります。 cache-firstを指定することで、クエリを投げた時に、キャッシュ上にデータがあるかを確認し、データがある場合は実際にリクエストすることなく、レスポンスを返します。

今回のケースでも、fetch-policyは明示していないので、cache-firstです(QUERY_A, QUERY_Bどちらも)。

Apolloクライアントでは、キャッシュに保存されているデータを問い合わせるたびに、各クエリのオブジェクトにユニークな識別子をつけるための「正規化」と呼ばれるプロセスを経ます。

通常、オブジェクトの __typenameフィールドにidフィールドを追加することで、各オブジェクトに、ユニークなキャッシュ識別子を割り当てを行います(例えば、userだと__typenameUserで取得したidが1であれば、識別子はUser:1となる)。そして、この識別子を見て「キャッシュ上にデータがあるかを確認し、データがある場合は実際にリクエストする」かを決定します。

Chromeの拡張機能であるApollo Client Devtoolsを利用することで、正規化後の__typenameとidに実際に何が格納されているかを確認することができます。

QUERY_Aでidを指定する前は、idを指定しているはずの、QUERY_BのUserオブジェクトのidも、cacheで空の状態であることが確認できました。 QUERY_Bでidを指定するようにすると、どちらのUserオブジェクトのidにも取得してきたidが入るようになります。

このような結果から、Apolloクライアントは、QUERY_Aではidを指定しなかったことにより、正規化プロセスが上手くいかず、複数回のリクエストをしてしまったと考えられます(どうして、QUERY_Bが3回、QUERY_Aが2回呼ばれるのかは、わかっていませんが....)。

終わりに

Apolloクライアントと正規化の時の識別子から生じる問題についての紹介でした。 それでは、良きApolloクライアント + GraphQL開発を!