Skip to content

Commit 87ac18e

Browse files
committed
feat: allow overriding canNextPage/canPreviousPage in pagination state
1 parent f5e9ba0 commit 87ac18e

4 files changed

Lines changed: 82 additions & 4 deletions

File tree

docs/framework/react/guide/pagination.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ No pagination row model is needed for server-side pagination, but if you have pr
9090

9191
#### Page Count and Row Count
9292

93-
The table instance will have no way of knowing how many rows/pages there are in total in your back-end unless you tell it. Provide either the `rowCount` or `pageCount` table option to let the table instance know how many pages there are in total. If you provide a `rowCount`, the table instance will calculate the `pageCount` internally from `rowCount` and `pageSize`. Otherwise, you can directly provide the `pageCount` if you already have it. If you don't know the page count, you can just pass in `-1` for the `pageCount`, but the `getCanNextPage` and `getCanPreviousPage` row model functions will always return `true` in this case.
93+
The table instance will have no way of knowing how many rows/pages there are in total in your back-end unless you tell it. Provide either the `rowCount` or `pageCount` table option to let the table instance know how many pages there are in total. If you provide a `rowCount`, the table instance will calculate the `pageCount` internally from `rowCount` and `pageSize`. Otherwise, you can directly provide the `pageCount` if you already have it. If you don't know the page count, you can just pass in `-1` for the `pageCount`, but the `getCanNextPage` and `getCanPreviousPage` row model functions will always return `true` in this case. When you do know whether more pages are available (for example, a server response that says "has more"), you can override this by setting the optional `canNextPage`/`canPreviousPage` values on the [pagination state](#pagination-state) directly.
9494

9595
```tsx
9696
import {
@@ -121,6 +121,10 @@ The `pagination` state is an object that contains the following properties:
121121

122122
- `pageIndex`: The current page index (zero-based).
123123
- `pageSize`: The current page size.
124+
- `canNextPage` _(optional)_: Explicitly overrides the value returned by `getCanNextPage()`. Useful for manual/server-side pagination (e.g. news feeds) where the total `pageCount`/`rowCount` is unknown.
125+
- `canPreviousPage` _(optional)_: Explicitly overrides the value returned by `getCanPreviousPage()`. Useful for manual/server-side pagination where the total `pageCount`/`rowCount` is unknown.
126+
127+
> **Note**: The pagination navigation APIs (`setPageIndex`, `setPageSize`, `nextPage`, etc.) preserve `canNextPage`/`canPreviousPage` when they update the state. However, if you replace the whole `pagination` object yourself (for example inside `onPaginationChange`), you must re-supply these flags to keep the overrides in effect.
124128
125129
For reactive reads that should re-render your UI, use `table.state.pagination`. In event handlers, you can read the current snapshot with `table.atoms.pagination.get()`, but this read does not subscribe the component to future changes.
126130

packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ import type { TableFeatures } from '../../types/TableFeatures'
55
export interface PaginationState {
66
pageIndex: number
77
pageSize: number
8+
/**
9+
* When manually controlling pagination, supply `canNextPage` explicitly to override the derived value. This is useful when working with news feeds or other server paginated data where `pageCount` or `rowCount` is unknown.
10+
*/
11+
canNextPage?: boolean
12+
/**
13+
* When manually controlling pagination, supply `canPreviousPage` explicitly to override the derived value. This is useful when working with news feeds or other server paginated data where `pageCount` or `rowCount` is unknown.
14+
*/
15+
canPreviousPage?: boolean
816
}
917

1018
export interface TableState_RowPagination {

packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,14 @@ export function table_getCanPreviousPage<
251251
TFeatures extends TableFeatures,
252252
TData extends RowData,
253253
>(table: Table_Internal<TFeatures, TData>) {
254-
return (table.atoms.pagination?.get()?.pageIndex ?? 0) > 0
254+
const pagination = table.atoms.pagination?.get()
255+
const { canPreviousPage, pageIndex } = pagination ?? {}
256+
257+
if (canPreviousPage !== undefined) {
258+
return canPreviousPage
259+
}
260+
261+
return (pageIndex ?? defaultPageIndex) > 0
255262
}
256263

257264
/**
@@ -269,10 +276,16 @@ export function table_getCanNextPage<
269276
TFeatures extends TableFeatures,
270277
TData extends RowData,
271278
>(table: Table_Internal<TFeatures, TData>) {
272-
const pageIndex = table.atoms.pagination?.get()?.pageIndex ?? defaultPageIndex
273-
279+
const pagination = table.atoms.pagination?.get()
280+
const pageIndex = pagination?.pageIndex ?? defaultPageIndex
281+
// Read `pageCount` before any early return so the reactivity system always
282+
// subscribes to its dependencies (pageSize atom + pre-paginated row model).
274283
const pageCount = table_getPageCount(table)
275284

285+
if (pagination?.canNextPage !== undefined) {
286+
return pagination.canNextPage
287+
}
288+
276289
if (pageCount === -1) {
277290
return true
278291
}

packages/table-core/tests/unit/features/row-pagination/rowPaginationFeature.utils.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,26 @@ describe('table_getCanPreviousPage', () => {
313313

314314
expect(table_getCanPreviousPage(table)).toBe(true)
315315
})
316+
317+
it('should honor an explicit canPreviousPage override on the first page', () => {
318+
const table = makeTable(25, {
319+
initialState: {
320+
pagination: { pageIndex: 0, pageSize: 10, canPreviousPage: true },
321+
},
322+
})
323+
324+
expect(table_getCanPreviousPage(table)).toBe(true)
325+
})
326+
327+
it('should honor an explicit canPreviousPage=false override past the first page', () => {
328+
const table = makeTable(25, {
329+
initialState: {
330+
pagination: { pageIndex: 1, pageSize: 10, canPreviousPage: false },
331+
},
332+
})
333+
334+
expect(table_getCanPreviousPage(table)).toBe(false)
335+
})
316336
})
317337

318338
describe('table_getCanNextPage', () => {
@@ -345,6 +365,39 @@ describe('table_getCanNextPage', () => {
345365

346366
expect(table_getCanNextPage(table)).toBe(false)
347367
})
368+
369+
it('should return false for a manual page count of 0', () => {
370+
const table = makeTable(25, {
371+
manualPagination: true,
372+
pageCount: 0,
373+
})
374+
375+
expect(table_getCanNextPage(table)).toBe(false)
376+
})
377+
378+
it('should honor a canNextPage=false override even when the page count is unknown (-1)', () => {
379+
const table = makeTable(25, {
380+
manualPagination: true,
381+
pageCount: -1,
382+
initialState: {
383+
pagination: { pageIndex: 0, pageSize: 10, canNextPage: false },
384+
},
385+
})
386+
387+
expect(table_getCanNextPage(table)).toBe(false)
388+
})
389+
390+
it('should honor a canNextPage=true override even when the page count is 0', () => {
391+
const table = makeTable(25, {
392+
manualPagination: true,
393+
pageCount: 0,
394+
initialState: {
395+
pagination: { pageIndex: 0, pageSize: 10, canNextPage: true },
396+
},
397+
})
398+
399+
expect(table_getCanNextPage(table)).toBe(true)
400+
})
348401
})
349402

350403
describe('page navigation', () => {

0 commit comments

Comments
 (0)