1
0

More than 3 years have passed since last update.

【Apollo Client】fetchMore関数でページネーションを実装する際の注意点

Last updated at Posted at 2021-08-06

ここでは、以下のスキーマとクエリを用いて説明していきます。offsetlimitで範囲を指定してフィードアイテムを取得するというものです。

スキーマ
type Query {
  feed(offset: Int, limit: Int): Feed!
}

type Feed {
  items: [FeedItem!]!
  total: Int!  
}

type FeedItem {
  id: ID!
  message: String!
}
クエリ
const FEED_QUERY = gql`
  query Feed($offset: Int, $limit: Int) {
    feed(offset: $offset, limit: $limit) {
      items {
        id
        message
      }
      total
    }
  }
`;

ページネーションの実装

既にフィールドポリシーの定義は終わっているものとして、prevボタンとnextボタンがあるページネーションを実装すると以下のようになります。

import { useQuery } from '@apollo/client';
import { useLocation, useHistory } from "react-router-dom";

const PER_PAGE = 10;
const calcOffset = (page) => ({ offset: (page - 1) * PER_PAGE });

const FeedData = () => {
  const { loading, data, fetchMore } = useQuery(FEED_QUERY, {
    variables: {
      offset: 0,
      limit: PER_PAGE
    },
  });

  const params = new URLSearchParams(useLocation().search);
  const page = params.get('page') ?? 1;

  const history = useHistory();

  if (loading) return <Loading/>;

  return (
    <Feed
      items={data.feed.items || []}
      onClickPrev={async () => {
        if (page > 1) {
          const variables = calcOffset(page - 1);
          await fetchMore({ variables });
          params.append('page', page - 1);
          history.push(`/?${params.toString()}`);
        }
      }}
      onClickNext={async () => {
        if (page < data.feed.total / PER_PAGE) {
          const variables = calcOffset(page + 1);
          await fetchMore({ variables });
          params.append('page', page + 1);
          history.push(`/?${params.toString()}`);
        }
      }}
    />
  );
}

しかし、この実装には1つ問題点があります。それは、ボタンをクリックするとキャッシュの有無に関わらず毎回fetchMore関数が実行されてしまうという点です。デベロッパーツールのネットワークタブを見てみるとわかりますが、fetchMore関数はキャッシュがあってもなくても毎回サーバーにリクエストを送ります。そのため、既にデータを取得したページに戻ってきたときに、無駄な通信を行ってしまうことになります。これを防ぐためには、fetchMore関数を実行する前に、readQueryreadFragment関数で必要なデータがキャッシュに保存されているか確認すれば良いです。実装を修正すると、以下のようになります。

- import { useQuery } from '@apollo/client';
+ import { useQuery, useApolloClient } from '@apollo/client';
import { useLocation, useHistory } from "react-router-dom";

const PER_PAGE = 10;
const calcOffset = (page) => ({ offset: (page - 1) * PER_PAGE });

const FeedData = () => {
  const { loading, data, fetchMore } = useQuery(FEED_QUERY, {
    variables: {
      offset: 0,
      limit: PER_PAGE
    },
  });

  const params = new URLSearchParams(useLocation().search);
  const page = params.get('page') ?? 1;

+  const client = useApolloClient();
+  const shouldFetchMore = (variables) => {
+    const { feed } = client.readQuery({
+      query: FEED_QUERY,
+      variables
+    });
+    return feed.items.length === 0;
+  }

  const history = useHistory();

  if (loading) return <Loading/>;

  return (
    <Feed
      items={data.feed.items || []}
      onClickPrev={async () => {
        if (page > 1) {
          const variables = calcOffset(page - 1);
-         await fetchMore({ variables });
+         if (shouldFetchMore(variables)) {
+          await fetchMore({ variables });
+         }
          params.append('page', page - 1);
          history.push(`/?${params.toString()}`);
        }
      }}
      onClickNext={async () => {
        if (page < data.feed.total / PER_PAGE) {
          const variables = calcOffset(page + 1);
-         await fetchMore({ variables });
+         if (shouldFetchMore(variables)) {
+          await fetchMore({ variables });
+         }
          params.append('page', page + 1);
          history.push(`/?${params.toString()}`);
        }
      }}
    />
  );
}

参考

1
0
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
1
0