この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
実現したいこと
初期ロードで 10 件のデータを画面に表示し、もっと見るボタンを押すと続きの 10 件を取得します。 このもっと見るボタンを押した時に取得した追加のデータを読み込み済みのデータにマージするために Apollo Client の typePolicies を設定します。
検証環境
- React "18.2.0"
- Apollo Client "3.6.9"
- GraphQL "16.5.0"
スキーマからのクエリや型の生成には codegen を利用しています。
利用する GraphQL API
サーバーサイド API のスキーマです。 first の値に取得したいデータ数、after にページネーションの次に取得するデータの ID を渡す作りになっています。
edges の中に画面に一覧表示したいデータが配列で入ります。
type SampleEdgeNode implements BaseSample {
id: ID!
totalQuantity: Int!
shopName: String!
issuedDate: String!
issuedAt: String!
}
type SampleEdge {
cursor: String!
node: SampleEdgeNode!
}
type SamplesConnection {
pageInfo: PageInfo!
edges: [SampleEdge!]
}
type Query {
samples(first: Int, after: String): SamplesConnection!
}
今回取得したいデータはこのようにネストされたデータ形式になります。
{
"data": {
"samples": {
"pageInfo": {
"hasPreviousPage": false,
"startCursor": "eyJpZCI6IlVhM2VmZTg5YWE4ZjlkOWQyZTlkMTQ0NmE3ZDI0OTYzMy0xMCIsImxpbmVVc2VySWQiOiJVYTNlZmU4OWFhOGY5ZDlkMmU5ZDE0NDZhN2QyNDk2MzMiLCJpc3N1ZWRBdCI6IjIwMjItMDQtMDFUMjA6MDA6MDArMDk6MDAifQ==",
"hasNextPage": true,
"endCursor": "eyJpZCI6IlVhM2VmZTg5YWE4ZjlkOWQyZTlkMTQ0NmE3ZDI0OTYzMy04IiwibGluZVVzZXJJZCI6IlVhM2VmZTg5YWE4ZjlkOWQyZTlkMTQ0NmE3ZDI0OTYzMyIsImlzc3VlZEF0IjoiMjAyMi0wNC0wMVQxODowMDowMCswOTowMCJ9",
"__typename": "PageInfo"
},
"edges": [
{
"cursor": "eyJpZCI6IlVhM2VmZTg5YWE4ZjlkOWQyZTlkMTQ0NmE3ZDI0OTYzMy0xMCIsImxpbmVVc2VySWQiOiJVYTNlZmU4OWFhOGY5ZDlkMmU5ZDE0NDZhN2QyNDk2MzMiLCJpc3N1ZWRBdCI6IjIwMjItMDQtMDFUMjA6MDA6MDArMDk6MDAifQ==",
"node": {
"id": "Ua3efe89aa8f9d9d2e9d1446a7d249633-10",
"totalQuantity": 2,
"shopName": "A店",
"issuedAt": "2022-04-01T20:00:00+09:00",
"issuedDate": "2022-04-01",
"__typename": "SampleEdgeNode"
},
"__typename": "SampleEdge"
},
{
"cursor": "eyJpZCI6IlVhM2VmZTg5YWE4ZjlkOWQyZTlkMTQ0NmE3ZDI0OTYzMy05IiwibGluZVVzZXJJZCI6IlVhM2VmZTg5YWE4ZjlkOWQyZTlkMTQ0NmE3ZDI0OTYzMyIsImlzc3VlZEF0IjoiMjAyMi0wNC0wMVQxOTowMDowMCswOTowMCJ9",
"node": {
"id": "Ua3efe89aa8f9d9d2e9d1446a7d249633-9",
"totalQuantity": 2,
"shopName": "A店",
"issuedAt": "2022-04-01T19:00:00+09:00",
"issuedDate": "2022-04-01",
"__typename": "SampleEdgeNode"
},
"__typename": "SampleEdge"
},
{
"cursor": "eyJpZCI6IlVhM2VmZTg5YWE4ZjlkOWQyZTlkMTQ0NmE3ZDI0OTYzMy04IiwibGluZVVzZXJJZCI6IlVhM2VmZTg5YWE4ZjlkOWQyZTlkMTQ0NmE3ZDI0OTYzMyIsImlzc3VlZEF0IjoiMjAyMi0wNC0wMVQxODowMDowMCswOTowMCJ9",
"node": {
"id": "Ua3efe89aa8f9d9d2e9d1446a7d249633-8",
"totalQuantity": 2,
"shopName": "A店",
"issuedAt": "2022-04-01T18:00:00+09:00",
"issuedDate": "2022-04-01",
"__typename": "SampleEdgeNode"
},
"__typename": "SampleEdge"
}
],
"__typename": "SampleConnection"
}
}
}
useQueryでデータを取得
上記の API からクエリでデータを取得します。
query samples($first: Int, $after: String) {
samples(first: $first, after: $after) {
pageInfo {
hasPreviousPage
startCursor
hasNextPage
endCursor
}
edges {
cursor
node {
id
totalQuantity
shopName
issuedAt
issuedDate
}
}
}
}
コンポーネントから useQuery を使ってデータを取得します。
const {loading, data} = useQuery<SamplesQuery>(SamplesDocument, {
variables: {
first: 3, // 一度のコールで取得するデータの数
cursor: null, // 初回読み込み時はnullを渡す
},
});
スキーマからの型の生成には codegen を利用しています。 以下が自動生成された内容になります。
export const SamplesDocument = gql`
query samples($first: Int, $after: String) {
samples(first: $first, after: $after) {
pageInfo {
hasPreviousPage
startCursor
hasNextPage
endCursor
}
edges {
cursor
node {
id
totalQuantity
shopName
issuedAt
issuedDate
}
}
}
}
`;
export type SamplesQuery = {
__typename?: "Query";
sampless: {
__typename?: "SampleConnection";
pageInfo: {
__typename?: "PageInfo";
hasPreviousPage: boolean;
startCursor?: string | null;
hasNextPage: boolean;
endCursor?: string | null;
};
edges?: Array<{
__typename?: "SampleEdge";
cursor: string;
node: {
__typename?: "SampleEdgeNode";
id: string;
totalQuantity: number;
shopName: string;
issuedAt: string;
issuedDate: string;
};
}> | null;
};
};
これで初期ロード時に 10 件のデータを取得できるようになりました。
fetchMore 関数で次の 10 件を取得
次にコンポーネントにもっと見るボタンと追加のデータを取得する関数を追加します。
samples.tsx
import {useQuery} from "@apollo/client";
import {FC, useCallback} from "react";
import {SamplesDocument, SamplesQuery} from "../../../generated/graphql";
import {LoadingPanel} from "../../atoms/LoadingPanel/LoadingPanel";
import {SystemError} from "../SystemError";
import {NotFoundSamples} from "./NotFoundSamples";
import {SampleCards} from "./SampleCards";
export const Samples: FC = () => {
const {loading, data, fetchMore} = useQuery<SamplesQuery>(SamplesDocument, {
variables: {
first: 3, // 一度のコールで取得するデータの数
cursor: null, // 初回ロード時はnullを渡す
},
});
const loadMore = useCallback(async () => {
// 次の10件を取得
await fetchMore({
variables: {
cursor: data?.samples.pageInfo.endCursor,
},
});
}, [data, fetchMore]);
if (loading) {
return <LoadingPanel />;
}
if (data === undefined) {
return <SystemError />;
}
return (
<div>
<div className="container bg-grayLight mx-auto h-screen">
<div className="px-6" data-test="samplesButton">
{/* データが1件以上なら一覧ページを表示し、0件の場合はNotFoundコンポーネントを表示する */}
{data.samples.edges != null && data?.samples.edges?.length > 0 ? (
<SampleCards data={data} loadMore={loadMore} />
) : (
<NotFoundSamples />
)}
</div>
</div>
</div>
);
};
この実装では loadMoreが呼ばれたときに次のデータ 10 件を取得します。 何も設定しないままだと、初回ロードの時に読み込んだデータを上書きする形で Apollo のキャッシュが上書きされます。
今回は初回ロードの時に取得したデータ 10 件に次の 10 件をプラスして表示させたいため、Apollo の typePolicies の設定でデータをマージします。
typePolicies で fetchMore で取得したデータを取得済みのデータとマージする
取得済みのデータと fetchMore で新しく取得したデータをマージする処理を typePolicies に記述します。
import {ApolloClient, InMemoryCache} from "@apollo/client";
import {SamplesQuery} from "./generated/graphql";
const endpoint = (import.meta.env.VITE_API_ENDPOINT as string) ?? "";
type SamplesData = SamplesQuery["samples"];
export const client = (liffIdToken: string | null) => {
const apolloClient = new ApolloClient({
uri: new URL("/graphql", endpoint).toString(),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
// フィールド名
samples: {
keyArgs: false,
// fetchMoreで取得したデータと取得済みのデータをマージ
// https://www.apollographql.com/docs/react/pagination/core-api/
merge(existing: SamplesData, incoming: SamplesData) {
return {
...(incoming ?? {}),
edges: [
...(existing?.edges ?? []),
...(incoming?.edges ?? []),
],
};
},
},
},
},
},
}),
headers: {
Authorization: IdToken ?? "",
},
});
return apolloClient;
};
今回のようにデータの形式がネストされている場合は以下のようにマージしたいデータを指定する必要があります。 以下の例では edges に配列で渡されるデータを取得済みのデータに追加してキャッシュするように指定しています。