Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clamp-page-index-when-reset-disabled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/table-core': patch
---

Clamp an out-of-range `pageIndex` to the last page when a data, filter, or grouping change shrinks the rows and `autoResetPageIndex` (or `autoResetAll`) is `false` with client-side pagination, instead of leaving the table on an empty page
2 changes: 1 addition & 1 deletion docs/framework/alpine/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ const table = createTable({

A common reason to set `autoResetPageIndex: false` is editing data while viewing the table (for example, inline cell editing). Every edit updates `data`, which recomputes the row models and would otherwise snap the user back to the first page. Setting the option to a static `false` keeps the current page when the row model recomputes. If you also use the expanding feature, pair it with `autoResetExpanded: false` so expanded rows do not collapse on edits.

Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages.
When `autoResetPageIndex` is off or `autoResetAll` is `false` and a data, filter, or grouping change reduces the row count, the table clamps any now-out-of-range `pageIndex` to the last page, so it does not show an empty page. With `manualPagination`, keeping `pageIndex` within the server's page range is still up to you.

### Pagination APIs

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/angular/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ readonly table = injectTable(() => ({

A common reason to set `autoResetPageIndex: false` is editing data while viewing the table (for example, inline cell editing). Every edit updates `data`, which recomputes the row models and would otherwise snap the user back to the first page. Setting the option to a static `false` keeps the current page when the row model recomputes. If you also use the expanding feature, pair it with `autoResetExpanded: false` so expanded rows do not collapse on edits.

Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages.
When `autoResetPageIndex` is off or `autoResetAll` is `false` and a data, filter, or grouping change reduces the row count, the table clamps any now-out-of-range `pageIndex` to the last page, so it does not show an empty page. With `manualPagination`, keeping `pageIndex` within the server's page range is still up to you.

### Pagination APIs

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/ember/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ const table = useTable(() => ({

A common reason to set `autoResetPageIndex: false` is editing data while viewing the table (for example, inline cell editing). Every edit updates `data`, which recomputes the row models and would otherwise snap the user back to the first page. Setting the option to a static `false` keeps the current page when the row model recomputes. If you also use the expanding feature, pair it with `autoResetExpanded: false` so expanded rows do not collapse on edits.

Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages.
When `autoResetPageIndex` is off or `autoResetAll` is `false` and a data, filter, or grouping change reduces the row count, the table clamps any now-out-of-range `pageIndex` to the last page, so it does not show an empty page. With `manualPagination`, keeping `pageIndex` within the server's page range is still up to you.

### Pagination APIs

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/lit/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ const table = this.tableController.table({

A common reason to set `autoResetPageIndex: false` is editing data while viewing the table (for example, inline cell editing). Every edit updates `data`, which recomputes the row models and would otherwise snap the user back to the first page. Setting the option to a static `false` keeps the current page when the row model recomputes. If you also use the expanding feature, pair it with `autoResetExpanded: false` so expanded rows do not collapse on edits.

Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages.
When `autoResetPageIndex` is off or `autoResetAll` is `false` and a data, filter, or grouping change reduces the row count, the table clamps any now-out-of-range `pageIndex` to the last page, so it does not show an empty page. With `manualPagination`, keeping `pageIndex` within the server's page range is still up to you.

### Pagination APIs

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/octane/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ const table = useTable({

A common reason to set `autoResetPageIndex: false` is editing data while viewing the table (for example, inline cell editing). Every edit updates `data`, which recomputes the row models and would otherwise snap the user back to the first page. Setting the option to a static `false` keeps the current page when the row model recomputes. If you also use the expanding feature, pair it with `autoResetExpanded: false` so expanded rows do not collapse on edits.

Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages.
When `autoResetPageIndex` is off or `autoResetAll` is `false` and a data, filter, or grouping change reduces the row count, the table clamps any now-out-of-range `pageIndex` to the last page, so it does not show an empty page. With `manualPagination`, keeping `pageIndex` within the server's page range is still up to you.

### Pagination APIs

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/preact/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ const table = useTable({

A common reason to set `autoResetPageIndex: false` is editing data while viewing the table (for example, inline cell editing). Every edit updates `data`, which recomputes the row models and would otherwise snap the user back to the first page. Setting the option to a static `false` keeps the current page when the row model recomputes. If you also use the expanding feature, pair it with `autoResetExpanded: false` so expanded rows do not collapse on edits.

Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages.
When `autoResetPageIndex` is off or `autoResetAll` is `false` and a data, filter, or grouping change reduces the row count, the table clamps any now-out-of-range `pageIndex` to the last page, so it does not show an empty page. With `manualPagination`, keeping `pageIndex` within the server's page range is still up to you.

### Pagination APIs

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/react/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ const table = useTable({

A common reason to set `autoResetPageIndex: false` is editing data while viewing the table (for example, inline cell editing). Every edit updates `data`, which recomputes the row models and would otherwise snap the user back to the first page. Setting the option to a static `false` keeps the current page when the row model recomputes. If you also use the expanding feature, pair it with `autoResetExpanded: false` so expanded rows do not collapse on edits.

Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages.
When `autoResetPageIndex` is off or `autoResetAll` is `false` and a data, filter, or grouping change reduces the row count, the table clamps any now-out-of-range `pageIndex` to the last page, so it does not show an empty page. With `manualPagination`, keeping `pageIndex` within the server's page range is still up to you.

### Pagination APIs

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/solid/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ const table = createTable({

A common reason to set `autoResetPageIndex: false` is editing data while viewing the table (for example, inline cell editing). Every edit updates `data`, which recomputes the row models and would otherwise snap the user back to the first page. Setting the option to a static `false` keeps the current page when the row model recomputes. If you also use the expanding feature, pair it with `autoResetExpanded: false` so expanded rows do not collapse on edits.

Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages.
When `autoResetPageIndex` is off or `autoResetAll` is `false` and a data, filter, or grouping change reduces the row count, the table clamps any now-out-of-range `pageIndex` to the last page, so it does not show an empty page. With `manualPagination`, keeping `pageIndex` within the server's page range is still up to you.

### Pagination APIs

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/svelte/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ const table = createTable({

A common reason to set `autoResetPageIndex: false` is editing data while viewing the table (for example, inline cell editing). Every edit updates `data`, which recomputes the row models and would otherwise snap the user back to the first page. Setting the option to a static `false` keeps the current page when the row model recomputes. If you also use the expanding feature, pair it with `autoResetExpanded: false` so expanded rows do not collapse on edits.

Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages.
When `autoResetPageIndex` is off or `autoResetAll` is `false` and a data, filter, or grouping change reduces the row count, the table clamps any now-out-of-range `pageIndex` to the last page, so it does not show an empty page. With `manualPagination`, keeping `pageIndex` within the server's page range is still up to you.

### Pagination APIs

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/vue/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ const table = useTable({

A common reason to set `autoResetPageIndex: false` is editing data while viewing the table (for example, inline cell editing). Every edit updates `data`, which recomputes the row models and would otherwise snap the user back to the first page. Setting the option to a static `false` keeps the current page when the row model recomputes. If you also use the expanding feature, pair it with `autoResetExpanded: false` so expanded rows do not collapse on edits.

Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages.
When `autoResetPageIndex` is off or `autoResetAll` is `false` and a data, filter, or grouping change reduces the row count, the table clamps any now-out-of-range `pageIndex` to the last page, so it does not show an empty page. With `manualPagination`, keeping `pageIndex` within the server's page range is still up to you.

### Pagination APIs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface TableState_RowPagination {
export interface TableOptions_RowPagination {
/**
* If set to `true`, pagination will be reset to the first page when page-altering state changes e.g. `data` is updated, filters change, grouping changes, etc.
*
* If set to `false` with client-side pagination, the current page is kept, but when a data, filter, or grouping change reduces the row count, a now-out-of-range page index is clamped to the last page.
*/
autoResetPageIndex?: boolean
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export function getDefaultPaginationState(): PaginationState {
* client-side pagination behavior allows it. Manual pagination opts out unless
* the reset options explicitly opt back in.
*
* When the reset is disabled for client-side pagination, an out-of-range
* page index is clamped to the last existing page instead, so a data,
* filter, or grouping change that shrinks the rows never leaves the table
* on a page that no longer exists. An in-range page index is left
* untouched.
*
* @example
* ```ts
* table_autoResetPageIndex(table)
Expand All @@ -41,6 +47,9 @@ export function table_autoResetPageIndex<
TFeatures extends TableFeatures,
TData extends RowData,
>(table: Table_Internal<TFeatures, TData>) {
const currentPageIndex =
table.atoms.pagination?.get()?.pageIndex ?? defaultPageIndex

if (
table.options.autoResetAll ??
table.options.autoResetPageIndex ??
Expand All @@ -50,11 +59,34 @@ export function table_autoResetPageIndex<
// already on the default page. Routing a no-op through the pagination
// handler would still run user `onPaginationChange` side effects (such
// as refetching) on every data, filter, sort, or grouping change.
const currentPageIndex =
table.atoms.pagination?.get()?.pageIndex ?? defaultPageIndex
if (currentPageIndex === defaultPageIndex) return
table_resetPageIndex(table, true)
return
}

clampPageIndex(table, currentPageIndex)
}

function clampPageIndex<TFeatures extends TableFeatures, TData extends RowData>(
table: Table_Internal<TFeatures, TData>,
currentPageIndex: number,
) {
// Manual pagination owns its page range, so only client-side pagination
// is clamped. Page 0 always exists, so skip computing the page count
// (and the row-model stages behind it) there.
if (table.options.manualPagination || currentPageIndex <= defaultPageIndex) {
return
}

const pageCount = table_getPageCount(table)
// A negative or non-finite count means the page range is unknown.
if (!Number.isFinite(pageCount) || pageCount < 0) return

const lastPageIndex = Math.max(0, pageCount - 1)
// Same no-op guard as the reset path: only route through the handler when
// the index actually has to move.
if (currentPageIndex <= lastPageIndex) return
table_setPageIndex(table, lastPageIndex)
}

/**
Expand Down
Loading