【GraphQL】フロントエンドでページネーションを実装する
GraphQL

【GraphQL】フロントエンドでページネーションを実装する

作成日:2021年12月03日
更新日:2021年12月03日

前回は、バックエンドでページネーションを追加しました。

graphql-backend-pagination

【GraphQL】バックエンドでページネーションを追加する

今回は、フロントエンドでページネーションを実装します。

ソースコードは、こちらです。

バックエンド:

フロントエンド:

まずは、前回バックエンドで作成した、skip と take を実装します。

Apollo Studio で、Books の GraphQL を作成します。

graphql
query FilteredBooks($skip: Int!, $take: Int!) {
books(skip: $skip, take: $take) {
id
title
author
category {
name
}
isRead
createdAt
}
}

Book ボタンをクリックすると、

image2

正常にデータを取得することができます。

Books の GraphQL をフロントエンドのbooksDataGraphQLと差し替えます。

tsx
const booksData = gql`
query FilteredBooks($skip: Int!, $take: Int!) {
books(skip: $skip, take: $take) {
id
title
author
category {
name
}
isRead
createdAt
}
}
`;

booksDatauseQueryvariableを指定しましょう。

tsx
const { data, error, loading } = useQuery(booksData, {
variables: {
skip: 2,
take: 3,
},
});

ブラウザで動作確認してみます。

image3

GraphQL が機能しています。

次にページネーションの UI を作成します。

ページネーションは、MUI を使います。

https://mui.com/components/pagination/ から Controlled pagination ページネーションを参考に、ページネーションを追加します。

tsx
import React, { useState } from "react";
import { gql, useMutation, useQuery } from "@apollo/client";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Title from "../components/Title";
import { Box, Button, Link, Pagination, Stack, styled } from "@mui/material";
import ModalBook from "../components/ModalBook";
type TypeBook = {
id: number;
title: string;
author: string;
category: {
name: string;
};
isRead: boolean;
createdAt: string;
};
const booksData = gql`
query FilteredBooks($skip: Int!, $take: Int!) {
books(skip: $skip, take: $take) {
id
title
author
category {
name
}
isRead
createdAt
}
}
`;
const updateBookData = gql`
mutation UpdateBook($updateBookId: Int!, $input: UpdateBookInput!) {
updateBook(id: $updateBookId, input: $input) {
errors {
message
}
book {
title
}
}
}
`;
export default function Book() {
const [page, setPage] = useState(1);
const { data, error, loading } = useQuery(booksData, {
variables: {
skip: 2,
take: 3,
},
});
const [updateBook] = useMutation(updateBookData);
if (error) return <div>データを取得することができませんでした</div>;
if (loading) return <div>Loading...</div>;
const { books } = data;
const handleChange = (event: React.ChangeEvent<unknown>, value: number) => {
setPage(value);
};
return (
<React.Fragment>
<CBox>
<Title>読書リスト</Title>
<ModalBook />
</CBox>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>タイトル</TableCell>
<TableCell>著者</TableCell>
<TableCell>カテゴリ</TableCell>
<TableCell>読了</TableCell>
<TableCell>作成日</TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{books.map((book: TypeBook) => (
<TableRow key={book.id}>
<TableCell>{book.id}</TableCell>
<TableCell>{book.title}</TableCell>
<TableCell>{book.author}</TableCell>
<TableCell>{book.category.name}</TableCell>
{book.isRead ? (
<TableCell>
<Button
size="small"
onClick={() => {
updateBook({
variables: {
updateBookId: book.id,
input: {
isRead: false,
},
},
});
}}
>
</Button>
</TableCell>
) : (
<TableCell>
<Button
size="small"
onClick={() => {
updateBook({
variables: {
updateBookId: book.id,
input: {
isRead: true,
},
},
});
}}
>
</Button>
</TableCell>
)}
<TableCell>
{`${new Date(Number(book.createdAt))}`
.split(" ")
.splice(1, 3)
.join(" ")}
</TableCell>
<TableCell>
<Link href={`/book/${book.id}`} underline="hover">
詳細画面
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Stack spacing={2}>
<Pagination
count={10}
page={page}
color="primary"
onChange={handleChange}
/>
</Stack>
</React.Fragment>
);
}
const CBox = styled(Box)({
display: "flex",
justifyContent: "space-between",
});

では、ページネーションが表示されるか、ブラウザで確認しましょう。

image4

ページネーションが表示されました。

ページネーションが機能するようにします。

books データは、今のところ 6 件あるので、3 件表示してボタンをクリックすると、次の 3 件が表示できるようにします。

Pagination の count は、全体の数から表示されている数を割った数にします。

また、Pagination の count が小数になることも考えられるので、小数になった場合、切り上げるようにします。

まずは、book の全体数を取得します。

バックエンドに戻って、book の全体数に関するスキーマとリゾルバを作成しましょう。

スキーマを作成するため、schema.ts を開きます。

typeDefsQuerytotalBooksを追加します。

ts
type Query {
totalBooks: Int!
books(isRead: Boolean, skip: Int, take: Int): [Book!]!
book(id: ID!): Book
categories: [Category!]!
category(id: ID!): Category
}

次は、リゾルバを作成します。

resolvers フォルダの Query.ts を開きます。

Query内に、totalBooksを作成します。

booksの全体数は、prismacountを使うことで取得することができます。

ts
totalBooks: (_: any, __: any, { prisma }: Context) => {
return prisma.book.count();
},

では、Apollo Studio で動作確認しましょう。

graphql
query TotalBooks {
totalBooks
}

image5

TotalBooks ボタンをクリックすると、

image6

books の全体数を取得することができました。

books の全体数を取得することができたので、フロントエンドの Book.tsx に戻ります。

totalBooksgqlを作成しましょう。

tsx
const totalBooks = gql`
query TotalBooks {
totalBooks
}
`;

useQuerytotalBooksのデータを取得します。

tsx
const { data, error, loading } = useQuery(totalBooks);

すると、All destructured elements are unused.というエラーが発生します。

image7

これは、totalBooksの data や error、loading とbooksDataの data や error、loading が被っているため、エラーになります。

エラーを解消するために、それぞれのの data や error、loading に名前をつけてあげます。

tsx
const {
data: totalBooksData,
error: totalBooksError,
loading: totalBooksLoaring,
} = useQuery(totalBooks);
const {
data: getBooksData,
error: booksDataError,
loading: booksDataLoading,
} = useQuery(booksData, {
variables: {
skip: 2,
take: 3,
},
});
tsx
if (booksDataError) return <div>データを取得することができませんでした</div>;
if (booksDataLoading) return <div>Loading...</div>;
const { books } = getBooksData;

これで、エラーを解消することができました。

一度にどのくらいデータを取得するか設定します。

tsx
const takeBooks = 3;

全体で何ページになるか設定します。

Pagination の count は、全体の数から表示されている数を割った数にしたいので、totalBooksData.totalBookstakeBooksで割ります。

また、小数点は、切り上げるようにします。

tsx
const totalPages = Math.ceil(totalBooksData.totalBooks / takeBooks);

totalPagesを Pagination の count に指定しましょう。

tsx
<Pagination
count={totalPages}
page={page}
color="primary"
onChange={handleChange}
/>

booksDatauseQueryで設定しているvariablesを修正します。

take は、takeBooks を指定します。

また、skip は、現在のページ数から 1 を引いて、take の数を掛けます。

例えば、現在のページが 2 ページ目で、取得するデータが 3 件だとすると、

(2 - 1) * 3 = 3 となります。

tsx
const {
data: getBooksData,
error: booksDataError,
loading: booksDataLoading,
} = useQuery(booksData, {
variables: {
skip: (page - 1) * takeBooks,
take: takeBooks,
},
});

totalBooksErrortotalBooksLoaringも設定しましょう。

tsx
if (totalBooksError || booksDataError)
return <div>データを取得することができませんでした</div>;
if (totalBooksLoaring || booksDataLoading) return <div>Loading...</div>;

一通り完了したので、動作確認します。

まずは、初めのページを確認すると、

image8

3 件分表示されています。

また、ページネーションも最大 2 ページまでとなっています。

では、ページネーションの『2』をクリックすると、

image9

次の 3 件分データを取得すことができました。

ページネーションの『1』をクリックすると、

image10

初めの 3 件分のデータを取得することができました。

ソースコードは、こちらです。

バックエンド:

フロントエンド:

© 2024あずきぱんウェブスタジオ