#NextでPagination付きのリストを実装したい
サーチエンジンにコンテンツを見て欲しいので、Nextかつインフィニットスクロールではなくてパジネーションを利用してみたかったです。
今回はMaterial-UIのPaginationコンポーネントを利用して、再利用可能なリストを作ってみたのですが、Material-UIやっぱり便利でした。
ページあたりの表示数を動的にするとかなるともう少し複雑になりそうです。
#使用ライブラリー
- react 17.0.1
- next 10.0.3
- @material-ui/lab ^4.0.0-alpha.57
- react-scroll ^1.8.1 (オプショナル)
#パジネーション付きリストの実装
今回Paginationコンポーネントに渡さないといけないのは以下の3点だけです。
- currentPage: 現在のページ
- pageCount: 総ページ数
- onChange: ページ変更時(ボタンクリック時)のイベント
それに加えて、当然ですがレンダーするアイテムのデータも必要になります。(後から考えたらこれは今回作るコンポーネント内で処理しなくても、childrenとして渡せばいいだけですが...)
なので以下のようなデータをバックエンドから送る前提です。
- items
- pageCount
currentPageはnextのrouterから取得します。
##PaginatedList
今回のパジネーション付きリストコンポーネント自体は単純です。リストとパジネーションをレンダーする+onChangeを処理するだけです。
onChangeでは、Paginationがpageを渡してくれるので、それを基にnextでrouter.pushします。
const PaginatedList: React.FC<PaginatedListProps> = ({
items,
pageCount,
renderItem,
}) => {
const router = useRouter();
const currentPage = Number(router.query.page) || 1;
const handlePageChange = (e: React.ChangeEvent, page: number) => {
const {pathname, query} = router;
query.page = `${page}`;
router.push({
pathname,
query,
});
};
return (
<div>
{/* このitemsとrenderItemは親からchildrenでlistを渡せばいいだけでした... */}
{items.map((item) => renderItem(item))}
<Pagination
currentPage={currentPage}
pageCount={pageCount}
onChange={handlePageChange}
/>
</div>
);
};
##nextのページでのgetServerSideProps
上記のonChangeでurlが変わる度に、getServerSidePropsでバックエンドからデータを持ってきます。
getServerSidePropsのcontextからquery.pageにアクセスするだけです。
export const getServerSideProps = async (ctx: NextPageContext) => {
const data = await fetchListItemsData(ctx.query.page || 1)
return {props: {data}};
};
##オプショナル:ページ変更時のスクロール
ページ変更時にリストのトップにスクロールさせたかったので、react-scrollを入れてこれも簡単に実装できました。
リスト全体をElementで囲って、onChangeにスクロールの処理を追加するだけです。Elementのnameは念のためオプショナルで渡せるようにしました。
const PaginatedList: React.FC<PaginatedListProps> = ({
name = 'paginated-list',
items,
pageCount,
renderItem,
}) => {
const router = useRouter();
const currentPage = Number(router.query.page) || 1;
const handlePageChange = (e: React.ChangeEvent, page: number) => {
const {pathname, query} = router;
query.page = `${page}`;
router.push({
pathname,
query,
});
//configはお好みに合わせて設定
scroller.scrollTo(name, {
duration: 500,
delay: 50,
smooth: true,
offset: -80,
});
};
return (
<Element name={name}>
{items.map((item) => renderItem(item))}
<Pagination
currentPage={currentPage}
pageCount={pageCount}
onChange={handlePageChange}
/>
</Element>
);
};
#まとめ
実質PaginationのonChangeからrouter.pushするだけでした。