virtual icon indicating copy to clipboard operation
virtual copied to clipboard

v3.13.8 doesn't scroll all the way to the bottom correctly

Open iscekic opened this issue 8 months ago • 11 comments

Describe the bug

There seems to be a regression in v3.13.8 vs v3.13.7 - exact same code in both cases:

// somewhere above
const measureElement: Parameters<typeof useVirtualizer>[0]["measureElement"] = (
  element,
) => element.getBoundingClientRect().height;

const estimateSize = () => 72;

const virtualizer = useVirtualizer({
  count: messages?.length || 0,
  getScrollElement: () => parentRef.current,
  estimateSize,
  measureElement,
  getItemKey: (index: number) =>
    messages?.[index] ? idExtractor(messages[index]) : nanoid(),
});

// somewhere below
const lastBeforeDateIndex = messages.length - 1; // this is a bit more complicated in the actual code, but most often resolves to this

// some other irrelevant logic, including a getVirtualItemForOffset call

virtualizer.scrollToIndex(Math.max(lastBeforeDateIndex, 0), {
  align: "start",
  behavior: "smooth",
});

For context, I have an infinite list in which items (messages) are occasionally appended. The items themselves have a dynamic size. When an item is appended, the list should be scrolled all the way to the bottom, so that the last item is visible.

Your minimal, reproducible example

/

Steps to reproduce

/

Expected behavior

3.13.8 retains the same behavior as 3.13.7

How often does this bug happen?

Every time

Screenshots or Videos

3.13.7 - scrolls correctly, bottom of last item is visible

Image

3.13.8 - doesn't scroll correctly, last item is cut off

Image

Platform

Brave on Linux, though it seems to reproduce everywhere

tanstack-virtual version

v3.13.8

TypeScript version

v5.8.3

Additional context

No response

Terms & Code of Conduct

  • [x] I agree to follow this project's Code of Conduct
  • [x] I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

iscekic avatar May 13 '25 11:05 iscekic

Thanks @iscekic for reporting the issue! I haven’t checked it yet, but it seems related to the loosened approxEqual logic introduced in https://github.com/TanStack/virtual/commit/60719f61b589d6f9d886e4f7c093217f6d693faf

piecyk avatar May 14 '25 15:05 piecyk

@piecyk I encountered the same issue. scrollToIndex no longer scrolls to the correct element.

Based on the code, the problem appears to be in replacing scrollSize with this.getTotalSize().

Code before:

const scrollSizeProp = this.options.horizontal
  ? 'scrollWidth'
  : 'scrollHeight'
const scrollSize = this.scrollElement
  ? 'document' in this.scrollElement
    ? this.scrollElement.document.documentElement[scrollSizeProp]
    : this.scrollElement[scrollSizeProp]
  : 0

const maxOffset = scrollSize - size

return Math.max(Math.min(maxOffset, toOffset), 0)

Code after:

const maxOffset = this.getTotalSize() - size

return Math.max(Math.min(maxOffset, toOffset), 0)

Previously, for window scrolling, the offset was limited by the document's content height. Now it's limited by the virtualized list's height instead. The farther the list is from the top of the page, the larger the error in the scroll position becomes. Even setting scrollMargin doesn't help, as according to the code, getTotalSize subtracts this value and has no affect on the result.

return Math.max(
  end - this.options.scrollMargin + this.options.paddingEnd,
  0,
)

I assume similar logic applies to non-window scrolling as well.

Please correct me if I've misunderstood anything.

There's another interesting issue related to this bug. If you try to scroll (using scrollToIndex) to an element that's far outside the visible area (i.e., not in the DOM), you can get stuck in an infinite scroll loop:

  1. An incorrect scroll offset is calculated for the given index.
  2. The page scrolls to that offset.
  3. The element is still not in the DOM => back to step 1.

In PR #995, the idea of adding a counter to break the recursion was raised. I think that would be quite helpful.

beliarh avatar Jun 19 '25 22:06 beliarh

@beliarh That makes a lot of sense, thanks for the detailed breakdown. I’ll try to look into it as soon as possible.

piecyk avatar Jun 23 '25 13:06 piecyk

Hi there !

I recently noticed this regression as well.

I experience the exact behavior described by @beliarh :

There's another interesting issue related to this bug. If you try to scroll (using scrollToIndex) to an element that's far outside the visible area (i.e., not in the DOM), you can get stuck in an infinite scroll loop:

An incorrect scroll offset is calculated for the given index. The page scrolls to that offset. The element is still not in the DOM => back to step 1.

I found a few workarounds.

Workarounds

  1. workaround for infinite scroll loop :

Instead of relying on scrollToIndex, I run my own virtualizer.getOffsetForIndex combined with virtualizer.scrollToOffset.

I didn't dig in the code, but I tested with my use case, it helps not being stuck in an infinite scroll loop.

  1. workaround for doesn't scroll all the way to the bottom

I first call virtualizer.scrollToOffset, then call either :

  • node.focus(), when I want to focus on a specific cell
  • node.scrollIntoView(), when I want to scroll to a specific cell

I guess this would work as well if you tried to scrollIntoView the tr node.

seedydocloop avatar Jun 24 '25 11:06 seedydocloop

Hey I’ve opened a PR that fix scrollToIndex behavior https://github.com/TanStack/virtual/pull/1029 If you're interested feel free to take a look and test it out! Feedback welcome.

piecyk avatar Jun 26 '25 13:06 piecyk

Can be tested via https://github.com/TanStack/virtual/pull/1029#issuecomment-3008435705 without waiting for it to be merged and published.

piecyk avatar Jun 26 '25 13:06 piecyk

Just released @tanstack/[email protected] should fix the scrolling issue.

piecyk avatar Jun 27 '25 12:06 piecyk

@piecyk I use function .scrollToOffset and have same issue, It happens from version v3.13.8. I updated to v3.13.12 but issue still happen

st-manhle avatar Jul 14 '25 08:07 st-manhle

I'm using scrollToIndex, and this is still happening on v3.13.12. I just tried with v3.13.7 and the issue was not occuring.

I am using useWindowVirtualizer.

georgesofianosgr avatar Jul 16 '25 17:07 georgesofianosgr

@st-manhle Hmm, so just to clarify, you're calling scrollToOffset to scroll to the bottom, but then the items expand in height and the scroll no longer sticks to the bottom?

Can you share exactly how you're calling scrollToOffset and what kind of content changes are happening after the scroll (e.g. dynamic height updates? That’ll help figure out if this is timing-related or something deeper introduced in 3.13.8.

@georgesofianosgr If you’re able to reproduce it in a small StackBlitz or CodeSandbox, that would make it much easier to debug.

piecyk avatar Aug 05 '25 06:08 piecyk

Possibly related: When using the scrollToOffset function with the offset from the virtualizer in the scrolled down state, the resulting offset will be size less than the original value suggestion the clamping is wrong.

bug-brain avatar Aug 26 '25 16:08 bug-brain