Skip to content

[virtual-core] Lazy measurement keys become inconsistent after a same-count data update #1270

Description

@tigerBeA

Describe the bug

In the single-lane measurement path, a stable getItemKey callback reading updated data can leave rendered item keys inconsistent with the current message IDs after a same-count update. With anchorTo: 'end', the same update can also lose the reading anchor even with followOnAppend: false.

The reproduction starts with messages 1–20 and Message 9 at the viewport top. Replacing the array with messages 2–21 leaves scrollTop at 400 instead of 350. The rendered range contains 8 key/ID mismatches; for example, Message 10 has VirtualItem.key 9. Message 9 moves 50 px above the viewport top.

Your minimal, reproducible example

Live cache reproduction

Pinned source · Source and local run instructions

The page uses published @tanstack/virtual-core@3.17.9 directly, with the standard element observers and scrolling function. It renders only the virtual range, including overscan, and uses exact 50 px row estimates in a 300 px viewport. Native CSS scroll anchoring is disabled. No network, dynamic row measurements, or custom scroll compensation is involved.

Steps to reproduce

  1. Open the cache page. Confirm Cached keys different from data IDs: 0, scrollTop: 400, and Message 9 at the viewport top.
  2. Click Trim oldest + append once. The array is replaced with messages 2–21, then setOptions receives the new options. The count and getItemKey function identity stay the same; the callback reads the current array.
  3. Observe 8 key/ID mismatches and scrollTop: 400. Message 10 is now at the viewport top; its cached key is 9.
  4. Click Reset cache example to repeat.

Expected behavior

After the update, VirtualItem.key should match the item represented by its index. The reading anchor should remain Message 9 at the viewport top, which requires scrollTop: 350 after the oldest 50 px row is removed. This expectation does not require following appended output; followOnAppend is explicitly disabled.

How often does this bug happen?

Every time with the steps above.

Platform

  • macOS 26.5.2
  • Chrome 152 (reported user-agent major version)
  • Verified on 2026-09-08 using a production Vite build

tanstack-virtual version

@tanstack/virtual-core@3.17.9

TypeScript version

Not applicable; the reproduction is JavaScript.

Additional context

Proposed fix: #1272.

Related: #1271 describes append-and-trim updates losing end following. This cache reproduction disables following and demonstrates a separate key-consistency failure.

The single-lane layout records positions and sizes immediately, but unread lazy items resolve their keys later through getItemKey. If the data changes between those steps, materialized items retain their old keys while unread items obtain keys from the new array. In this fixture, setOptions reads the old first/last keys lazily after the array has changed, so its edge comparison misses the data update.

The suspected invariant is that a measurement's key should belong to the same layout build as its position and size. The fixture does not read the private measurement cache or pre-read the first/last items to influence change detection.

Terms & Code of Conduct

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions