📖
pagination(無限スクロール)の実装
使用技術
front
- nextjs
- chakraui
- tanstackquery
back
- go
概要
スクロールしてスクロールの一番下までいくと追加で読み込んでくれる無限スクロールを実装する
一度に大量の取得を行わないので時間がかからずuxが良い
リクエスト
{
limit // 一度に取得するデータ数
after // 次回どこから取得するか(idなど)
}
レスポンス
{
list:[data1, data2...]
pagination: {
nextCursor: 'afterが入る'
}
}
frontの実装
- クエリ
initialPageParamが初めのafterになる
その後getNextPageParamによって返ってきたnextCursorがafterとして次にリクエストされる
import { useInfiniteQuery } from '@tanstack/react-query'
export const usePaginatedData = (
limit = 30 //デフォルト
) => {
return useInfiniteQuery({
queryKey: ['pagingData', limit],
queryFn: ({ pageParam }) =>
api.List({
after: pageParam ?? undefined,
limit: limit,
}),
initialPageParam: undefined,
getNextPageParam: (lastPage: PaginatedData) => {
return lastPage.pagination?.nextCursor || undefined
},
})
}
- 機構
useEffectでobserverからスクロールを監視
スクロールが一番下までいくとfetchNextPageを発火
const {
data: paginatedData,
error,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = usePaginatedData()
---
// 以下の形でpropsとして渡した
pagination={
fetchNextPage,
hasNextPage,
isFetchingNextPage
}
// スクロールの監視
const loadMoreRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
if (!pagination) {
return
}
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && pagination.hasNextPage) {
pagination.fetchNextPage()
}
},
{
root: containerRef.current,
threshold: 0.1,
}
)
const ref = loadMoreRef.current
if (ref) {
observer.observe(ref)
}
return () => {
if (ref) {
observer.unobserve(ref)
}
}
}, [pagination])
- 表示
paginationでバラバラのデータを結合して渡す
スクロールの最後に目印としてrefを追加する
// データの結合
const data = useMemo(() => {
return paginatedData?.pages.flatMap((page) => page.manualCosts) || []
}, [paginatedData])
<VStack>
data.map((item, idx) => {
// 各itemの表示
})
<div ref={loadMoreRef}>{pagination?.isFetchingNextPage ? <Spinner /> : null}
</VStack>
backendの実装(usecase層のみ)
エラー処理などは省略している
一つ多く取ってきてnextCursorを定めている
func (u *usecase) List(ctx context.Context after *uuid.UUID limit int) (*ListData, err) {
queryLimit := limit + 1
var items []model.Item
var err error
if after != nil {
baseItem, err := datastore.get(ctx, *after) // 基準となるitemを取得
// 基準以降のデータを取得(順番はソートによる)
items, err = datastore.list(ctx, queryLimit, where(sortField <= baseItem.sortField))
} else {
items, err = datastore.list(ctx, queryLimit)
}
// 1つ多く取ってきてるのでそこからnextCursorを定め, 本来の数のデータを返す
var nextCursor *uuid.UUID
if len(items) == queryLimit {
nextCursor = &items[limit].ID
items = items[limit]
}
return &listData{
list: items,
pagination: {
nextCursor: nextCursor
}
}, nil
}
Discussion