Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gridSystem.ts): call onEndReached when the data is less than cont… #1166

Merged
merged 5 commits into from
Dec 1, 2024

Conversation

prasannamestha
Copy link
Contributor

This PR fixes this bug: #1087

@@ -253,17 +253,21 @@ export const gridSystem = /*#__PURE__*/ u.system(
} else {
startIndex = perRow * floor((startOffset + rowGap) / (itemHeight + rowGap))
endIndex = perRow * ceil((endOffset + rowGap) / (itemHeight + rowGap)) - 1
endIndex = min(totalCount - 1, max(endIndex, perRow - 1))
endIndex = min((data?.length || totalCount || 0) - 1, max(endIndex, perRow - 1))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll prepare a video later today to explain but here's the gist:

When both data and totalCount is passed to VirtuosoGrid - the endIndex calculation was faulty. Now looking at this after a good night sleep, it seems like we might not require this as we update totalCount below. I'll get back on this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay here's the explanation why this change is necessary:

In a component with paginated data (data received from useInfiniteQuery), onEndReached function fires the callback to load more data - that eventually increases the data size. Additionally there might be a different totalIndex specified to calculate the container height. So usually totalCount could be greater than or equal to data.length

Hence the endIndex should consider the min of data.length or totalCount to get the right value

Comment on lines 279 to 284
u.combineLatest(data, totalCount),
u.filter(([data]) => data !== null),
u.map(([data, totalCount]) => {
return Math.max(data!.length, totalCount)
}),
u.distinctUntilChanged()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you changing this? The component should accept either data or total count, and if data is supplied, it should control the total count value throughout the rest of the implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary to calculate the right totalCount when both data and totalCount is passed to VirtuosoGrid:

<VirtuosoGrid
  data={arrayOf20Items}
  totalCount={100}
/>

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having both data and total count is not a valid configuration and will result in a weird inconsistent implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it is a common use case for something like a dynamic list (or infinite list)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see how one can potentially achieve some elaborate implementation using a combination of both props and load on demand, and conditional rendering depending on data being present or not.

However, this is way beyond trivial, and supporting this would need a good set of examples and tests. If these are not present, the capability would just result in people struggling infinitely and claiming "bugs" that I need to deal with.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.. Gotcha, maybe I touched a bit more code than necessary. I'll make some changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petyosi take a look now, I have added an example page and updated code to only touch endReached fn.

Comment on lines 262 to 270
// const rowCount = ceil(totalCount / perRow) // this is the total number of rows based on `totalCount`
const rowsVisibleCount = ceil((data?.length || totalCount || 0) / perRow) // number of rows rendered on screen

return { items, offsetTop: top, offsetBottom, top, bottom, itemHeight, itemWidth } as GridState
// const totalHeight = rowCount * itemHeight + (rowCount - 1) * rowGap // this is the total height that the container should occupy
const virtualHeight = rowsVisibleCount * itemHeight + (rowsVisibleCount - 1) * rowGap // height visible

const offsetBottom = virtualHeight - bottom

return { items, offsetTop: top, offsetBottom, top, bottom, itemHeight, itemWidth, data } as GridState
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I am missing something, this change should not do anything. See my comment on the relationship between totalCount and data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, I added data here for testing, forgot removing it. Will update this later today

)
}),
u.map(([[_, data]]) => {
return data?.length
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does that work if the grid has no data and uses totalCount?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. It does work but now I'm thinking maybe I don't need this. This change was made before updating the totalCount logic, but I'll revisit. Maybe I was way too sleepy while raising this PR

u.withLatestFrom(totalCount, hasScrolled, data),
u.filter(([[gridState, data], _totalCount, hasScrolled]) => {
const lastIndex = gridState.items[gridState.items.length - 1].index
const isLastItemRendered = lastIndex === (data?.length || 0) - 1
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why data length and not totalCount?


// User has not scrolled, so check whether grid is fully rendered
const isFullyRendered =
gridState.bottom > 0 && gridState.itemHeight > 0 && gridState.offsetBottom === 0 && gridState.items.length === data?.length
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why data length and not totalCount?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's strange but using totalCount here doesn't fire more than once. But I think I found a way to get around it. Hold on, sending an update

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petyosi check now :)

@petyosi petyosi merged commit 7a80ea2 into petyosi:master Dec 1, 2024
Copy link

github-actions bot commented Dec 1, 2024

🎉 This PR is included in version 4.12.3 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants