From 1679287cb639d4fcfef41608ca982f3bf4609b04 Mon Sep 17 00:00:00 2001 From: Minwook Shin <163576506+minwookshin@users.noreply.github.com> Date: Mon, 28 Sep 2026 21:20:50 -0400 Subject: [PATCH] fix(virtual-core): reconcile index scrolls after pending measurements --- .changeset/tidy-scroll-measurements.md | 5 + .../e2e/app/cached-measurements/main.tsx | 28 ++- .../e2e/app/test/cached-measurements.spec.ts | 28 +++ packages/virtual-core/src/index.ts | 38 +++- packages/virtual-core/tests/index.test.ts | 163 +++++++++++++++++- 5 files changed, 253 insertions(+), 9 deletions(-) create mode 100644 .changeset/tidy-scroll-measurements.md diff --git a/.changeset/tidy-scroll-measurements.md b/.changeset/tidy-scroll-measurements.md new file mode 100644 index 000000000..463d61c40 --- /dev/null +++ b/.changeset/tidy-scroll-measurements.md @@ -0,0 +1,5 @@ +--- +'@tanstack/virtual-core': patch +--- + +Keep scrollToIndex reconciliation active until pending ResizeObserver measurements can update its target, including when useAnimationFrameWithResizeObserver is enabled. Respect an external scroll away from a reached target during this settling period. diff --git a/packages/react-virtual/e2e/app/cached-measurements/main.tsx b/packages/react-virtual/e2e/app/cached-measurements/main.tsx index 57c02899e..9fe215bcb 100644 --- a/packages/react-virtual/e2e/app/cached-measurements/main.tsx +++ b/packages/react-virtual/e2e/app/cached-measurements/main.tsx @@ -1,5 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' +import { flushSync } from 'react-dom' import { useVirtualizer } from '@tanstack/react-virtual' const items = Array.from({ length: 20 }, (_, i) => ({ @@ -11,14 +12,19 @@ const items = Array.from({ length: 20 }, (_, i) => ({ const App = () => { const parentRef = React.useRef(null) const [hidden, setHidden] = React.useState(false) + const [expanded, setExpanded] = React.useState(false) const rowVirtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, - estimateSize: (i) => items[i].height, + estimateSize: (i) => items[i].height - 10, getItemKey: (i) => items[i].id, useCachedMeasurements: hidden, directDomUpdates: true, + overscan: items.length, + useAnimationFrameWithResizeObserver: new URLSearchParams( + location.search, + ).has('raf'), }) return ( @@ -26,6 +32,24 @@ const App = () => { + + +
+ {rowVirtualizer.isScrolling ? 'scrolling' : 'idle'} +