fix(bindx-dataview): keep the page index within the known page count - #132
Merged
Merged
Conversation
…e total shrinks Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TPdGxo89UiPkZHxreAxvFD
usePagingState stored the requested page index and derived the query
offset from it without looking at the total. When the total shrank (a
narrower filter) or a stored index outlived its result set, the grid
queried an offset past the last row and rendered an empty page under a
non-zero count ("page 15 of 1").
The effective page index is now the requested one clamped to
[0, totalPages - 1] once the total is known, so state, queryOffset,
hasNext and hasPrevious agree on the first render after the count lands.
An effect writes the clamped value back so the stored index and the
relative navigation (next/previous) continue from the page on screen.
While the total is unknown the requested index is kept as is.
Closes #127
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ge3kRufpi9b9HMBSBjRze5
…query Without a count (an adapter that implements no count query, or a failing one), the grids infer the total from a page shorter than the limit. On the render that changed the offset, that inference paired the new offset with the rows of the previous query. With the page-index clamp this cascaded: an empty page at offset 28 set a total of 26, the clamp moved to offset 26, the same stale rows set 24, and so on down to page 0 with a total of 0. Rows past the first page became unreachable. - useEntityList reports $isRefetching from the render that changes the options or the queryKey, not only after its data-loading effect has run, so a ready result never presents rows of an earlier query as current. - DataGrid, SelectDataView and HasManyDataGrid share usePagingTotalCount, which infers a total only from a page that answers the current query. HasManyDataGrid records the options its rows were loaded for. - An empty page past offset 0 implies its offset as an upper bound, so the clamp steps back one page per query and stops on the last page with rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ge3kRufpi9b9HMBSBjRze5
…unted Without a count, an empty page past offset 0 only bounds the total from above. Stepping back one page per query cost one round trip per page, so a stored page 500 took 500 queries. usePagingTotalCount now keeps the bounds the loaded pages prove (a full page raises the lower one, an empty page lowers the upper one) and probes the page holding the middle row, so the interval halves with every query. A short page, or bounds that meet, set the exact total. The bounds belong to one row set and restart when the filter changes. Also covers HasManyDataGrid with a relation that reports no total, and states in the useEntity doc when its $isRefetching turns on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ge3kRufpi9b9HMBSBjRze5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #127.
The bug
usePagingStatestored the requested page index and computedqueryOffsetfrom it without looking at the total. When the total shrank below the current page (a toolbar filter that narrows the result, or a page index restored fromcurrentPageStateStoragethat outlived its result set), the grid queried an offset past the last row and rendered its empty state under a non-zero count, with the paging UI reading "page 15 of 1".The fix
[0, totalPages - 1]once the total is known.state.pageIndex,queryOffset,hasNextandhasPreviousall read the clamped value, so they agree on the first render after the count lands — there is no render with the out-of-range offset.next()/previous()continue from it.totalPagesis at least 1).Grids without a count
With an adapter that implements no count query (or a count query that fails),
DataGrid,SelectDataViewandHasManyDataGridinfer the total from a page shorter than the limit (queryOffset + itemCount). That inference used to run on the render that changed the offset, against the rows of the previous query. Combined with the clamp it cascaded: an empty page at offset 28 set a total of 26, the clamp moved to offset 26, the same stale rows set 24, and so on down to page 0 with a total of 0 — rows past the first page became unreachable.useEntityListnow reports$isRefetchingfrom the render that changes the options or thequeryKey, not only after its data-loading effect has run. A ready result never presents rows of an earlier query as current.usePagingTotalCount, which infers a total only from a page that answers the current query.HasManyDataGridrecords the options its rows were loaded for.usePagingTotalCountkeeps the bounds the loaded pages prove — a full page raises the lower bound, an empty page lowers the upper one — and moves to the page holding the middle row, so the interval halves with every query. A page shorter than the limit, or bounds that meet, give the exact total. The bounds belong to one row set and restart when the filter changes.useEntitydoc now says when its$isRefetchingturns on: once the re-fetch starts, one render after thequeryKeychange.useEntityListreports it from the render that changes the query.Behaviour to review
Tests
tests/react/dataview/pagingPageIndexOutOfRange.test.tsx— hook level: the failing repro from the issue, the stored index written back, a stored index kept while the total is unknown, clamping to the last existing page and navigating from it, a total of zero. 4 of the 5 cases fail onmain.tests/react/dataview/dataGridPageIndexOutOfRange.test.tsx— DataGrid level, each asserting the exact list-query offsets:1 + ceil(log2(1000))list queries;tests/react/dataview/hasManyDataGridPageIndexOutOfRange.test.tsx—HasManyDataGridwith a relation that reports no total: a stored page 14 over 7 rows ends on "Page 4 of 4" with offsets 28, 14, 6. It fails ("Page 2 of 2") without theoptionsKeycheck.tests/react/hooks/useEntityList/refetchingOnQueryChange.test.tsx— no render after an offset change reports the previous offset's rows as settled. Fails without theuseEntityListchange.Checks:
bun run typecheckcleanbun run lint0 errorsbun run test2119 pass, 0 fail🤖 Generated with Claude Code
https://claude.ai/code/session_01Ge3kRufpi9b9HMBSBjRze5