diff --git a/.changeset/hungry-buses-shop.md b/.changeset/hungry-buses-shop.md new file mode 100644 index 00000000..5ffca860 --- /dev/null +++ b/.changeset/hungry-buses-shop.md @@ -0,0 +1,5 @@ +--- +'@tanstack/virtual-core': patch +--- + +revert(virtual-core): "notify framework when count changes" 2542c5a diff --git a/docs/api/virtualizer.md b/docs/api/virtualizer.md index d9110f53..930a2f6b 100644 --- a/docs/api/virtualizer.md +++ b/docs/api/virtualizer.md @@ -136,9 +136,9 @@ The position where the list is scrolled to on render. This is useful if you are getItemKey?: (index: number) => Key ``` -This function is passed the index of each item and should return a unique key for that item. The default functionality of this function is to return the index of the item, but you should override this when possible to return a unique identifier for each item across the entire set. +This function is passed the index of each item and should return a unique key for that item. The default functionality of this function is to return the index of the item, but you should override this when possible to return a unique identifier for each item across the entire set. -**Important:** In React (and similar reactive frameworks), this function **must be memoized** (e.g., using `useCallback`) to prevent infinite re-render loops that will crash your application. Without memoization, the virtualizer will detect the function reference change on every render and trigger measurement recalculation, which causes another render, creating an infinite loop. +**Note:** The virtualizer automatically invalidates its measurement cache when measurement-affecting options change, ensuring `getTotalSize()` and other measurements return fresh values. While the virtualizer intelligently tracks which options actually affect measurements, it's still better to memoize `getItemKey` (e.g., using `useCallback` in React) to avoid unnecessary recalculations. ### `rangeExtractor` diff --git a/packages/virtual-core/src/index.ts b/packages/virtual-core/src/index.ts index fb7bb5e6..6c1cafa4 100644 --- a/packages/virtual-core/src/index.ts +++ b/packages/virtual-core/src/index.ts @@ -645,11 +645,6 @@ export class Virtualizer< }, { key: false, - skipInitialOnChange: true, - onChange: () => { - // Notify when measurement options change as they affect total size - this.notify(this.isScrolling) - }, }, ) diff --git a/packages/virtual-core/tests/index.test.ts b/packages/virtual-core/tests/index.test.ts index 624fb805..6a00c398 100644 --- a/packages/virtual-core/tests/index.test.ts +++ b/packages/virtual-core/tests/index.test.ts @@ -120,3 +120,41 @@ test('should correctly recalculate lane assignments when lane count changes', () }).not.toThrow() }) }) + +test('should update getTotalSize() when count option changes (filtering/search)', () => { + const virtualizer = new Virtualizer({ + count: 100, + estimateSize: () => 50, + getScrollElement: () => null, + scrollToFn: vi.fn(), + observeElementRect: vi.fn(), + observeElementOffset: vi.fn(), + }) + + expect(virtualizer.getTotalSize()).toBe(5000) // 100 × 50 + + // Simulate filtering - reduce count to 20 + virtualizer.setOptions({ + count: 20, + estimateSize: () => 50, + getScrollElement: () => null, + scrollToFn: vi.fn(), + observeElementRect: vi.fn(), + observeElementOffset: vi.fn(), + }) + + // getTotalSize() should immediately return updated value (not stale) + expect(virtualizer.getTotalSize()).toBe(1000) // 20 × 50 + + // Restore full count + virtualizer.setOptions({ + count: 100, + estimateSize: () => 50, + getScrollElement: () => null, + scrollToFn: vi.fn(), + observeElementRect: vi.fn(), + observeElementOffset: vi.fn(), + }) + + expect(virtualizer.getTotalSize()).toBe(5000) // 100 × 50 +})