例
ここでは、以下のスキーマとクエリを用いて説明していきます。offset
とlimit
で範囲を指定してフィードアイテムを取得するというものです。
スキーマ
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
関数を実行する前に、readQuery
やreadFragment
関数で必要なデータがキャッシュに保存されているか確認すれば良いです。実装を修正すると、以下のようになります。
- 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()}`);
}
}}
/>
);
}
参考