ページネーション
RTK Queryには、組み込みのページネーション機能は含まれていません。しかし、RTK Queryは標準のインデックスベースのページネーションAPIとの統合を容易にします。これは、実装する必要がある最も一般的な形式のページネーションです。
ページネーションレシピ
ページarg
を受け入れるエンドポイントを設定する
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
interface Post {
id: number
name: string
}
interface ListResponse<T> {
page: number
per_page: number
total: number
total_pages: number
data: T[]
}
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (builder) => ({
listPosts: builder.query<ListResponse<Post>, number | void>({
query: (page = 1) => `posts?page=${page}`,
}),
}),
})
export const { useListPostsQuery } = api
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (builder) => ({
listPosts: builder.query({
query: (page = 1) => `posts?page=${page}`,
}),
}),
})
export const { useListPostsQuery } = api
page
状態変数をインクリメントして次のページをトリガーする
const PostList = () => {
const [page, setPage] = useState(1)
const { data: posts, isLoading, isFetching } = useListPostsQuery(page)
if (isLoading) {
return <div>Loading</div>
}
if (!posts?.data) {
return <div>No posts :(</div>
}
return (
<div>
{posts.data.map(({ id, title, status }) => (
<div key={id}>
{title} - {status}
</div>
))}
<button onClick={() => setPage(page - 1)} isLoading={isFetching}>
Previous
</button>
<button onClick={() => setPage(page + 1)} isLoading={isFetching}>
Next
</button>
</div>
)
}
ページネーションクエリの自動再取得
タグの無効化を利用してRTK Queryで自動再取得を実行することは、一般的なユースケースです。
これをページネーションと組み合わせる際の潜在的な落とし穴は、ページネーションクエリが特定の時点では*部分的な*リストのみを提供する可能性があり、したがって現在表示されていないページに該当するエンティティIDのタグをprovide
しないことです。特定のエンティティが削除され、それが以前のページに該当する場合、ページネーションクエリはその特定のIDのタグを提供せず、データの再取得をトリガーするために無効化されません。その結果、1つ上にシフトする必要がある現在のページの項目はシフトせず、項目とページの総数が正しくない可能性があります。
これを克服するための一つの戦略は、削除された項目がそのページに*現在*提供されていなくても、delete
ミューテーションが常にページネーションクエリをinvalidate
することを保証することです。ページネーションクエリで'PARTIAL-LIST'
IDを持つ'Posts'
タグをproviding
し、それに影響を与える可能性のあるミューテーションに対して対応するタグをinvalidating
することにより、 抽象タグIDによる高度な無効化の概念を活用できます。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
interface Post {
id: number
name: string
}
interface ListResponse<T> {
page: number
per_page: number
total: number
total_pages: number
data: T[]
}
export const postApi = createApi({
reducerPath: 'postsApi',
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
listPosts: build.query<ListResponse<Post>, number | void>({
query: (page = 1) => `posts?page=${page}`,
providesTags: (result, error, page) =>
result
? [
// Provides a tag for each post in the current page,
// as well as the 'PARTIAL-LIST' tag.
...result.data.map(({ id }) => ({ type: 'Posts' as const, id })),
{ type: 'Posts', id: 'PARTIAL-LIST' },
]
: [{ type: 'Posts', id: 'PARTIAL-LIST' }],
}),
deletePost: build.mutation<{ success: boolean; id: number }, number>({
query(id) {
return {
url: `post/${id}`,
method: 'DELETE',
}
},
// Invalidates the tag for this Post `id`, as well as the `PARTIAL-LIST` tag,
// causing the `listPosts` query to re-fetch if a component is subscribed to the query.
invalidatesTags: (result, error, id) => [
{ type: 'Posts', id },
{ type: 'Posts', id: 'PARTIAL-LIST' },
],
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const postApi = createApi({
reducerPath: 'postsApi',
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
listPosts: build.query({
query: (page = 1) => `posts?page=${page}`,
providesTags: (result, error, page) =>
result
? [
// Provides a tag for each post in the current page,
// as well as the 'PARTIAL-LIST' tag.
...result.data.map(({ id }) => ({ type: 'Posts', id })),
{ type: 'Posts', id: 'PARTIAL-LIST' },
]
: [{ type: 'Posts', id: 'PARTIAL-LIST' }],
}),
deletePost: build.mutation({
query(id) {
return {
url: `post/${id}`,
method: 'DELETE',
}
},
// Invalidates the tag for this Post `id`, as well as the `PARTIAL-LIST` tag,
// causing the `listPosts` query to re-fetch if a component is subscribed to the query.
invalidatesTags: (result, error, id) => [
{ type: 'Posts', id },
{ type: 'Posts', id: 'PARTIAL-LIST' },
],
}),
}),
})
一般的なページネーションの例
次の例では、最初のクエリでは「読み込み中」と表示されますが、次に進むにつれて、キャッシュされていないクエリが実行されている間、次/前ボタンを*取得*インジケータとして使用します。戻る際には、キャッシュされたデータが瞬時に提供されます。