diff --git a/change/@fluentui-contrib-react-virtualizer-18336692-38bc-47a7-9dfb-0d0a2517f00f.json b/change/@fluentui-contrib-react-virtualizer-18336692-38bc-47a7-9dfb-0d0a2517f00f.json new file mode 100644 index 00000000..df1e16e1 --- /dev/null +++ b/change/@fluentui-contrib-react-virtualizer-18336692-38bc-47a7-9dfb-0d0a2517f00f.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: prevent RangeError: Invalid array length when numItems decreases while scrolled near the bottom", + "packageName": "@fluentui-contrib/react-virtualizer", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index 5d69dc53..82f85a94 100644 --- a/packages/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -163,7 +163,10 @@ export function useVirtualizer_unstable( return []; } const newIndex = Math.max(_newIndex, 0); - const arrayLength = Math.min(virtualizerLength, numItems - newIndex); + const arrayLength = Math.min( + virtualizerLength, + Math.max(0, numItems - newIndex) + ); // Always create fresh React elements to prevent key reconciliation issues // when items order changes or when re-rendering is needed. @@ -319,7 +322,7 @@ export function useVirtualizer_unstable( }, [itemSize, numItems, gap]); const calculateBefore = React.useCallback(() => { - const currentIndex = Math.min(actualIndex, numItems - 1); + const currentIndex = Math.max(0, Math.min(actualIndex, numItems - 1)); if (!getItemSize) { // The missing items from before virtualization starts height return currentIndex * (itemSize + gap); diff --git a/packages/react-virtualizer/src/testing/useVirtualizer.test.ts b/packages/react-virtualizer/src/testing/useVirtualizer.test.ts index a45eae73..06b696b2 100644 --- a/packages/react-virtualizer/src/testing/useVirtualizer.test.ts +++ b/packages/react-virtualizer/src/testing/useVirtualizer.test.ts @@ -170,6 +170,43 @@ describe('useVirtualizer', () => { expect(rowFunc).toHaveBeenCalled(); }); + it('should not crash with RangeError when numItems decreases while scrolled near the bottom', () => { + // Regression test for https://github.com/microsoft/fluentui-contrib/issues/607 + // When filtering reduces numItems while actualIndex > new numItems, + // renderChildRows computed a negative arrayLength causing "RangeError: Invalid array length" + const virtualizerLength = 50; + const containerSizeRef = { current: 300 }; + + const rowFunc = (index: number) => index; + + // Simulate the user being scrolled near the bottom of a 100-item list (index 90) + const scrolledIndex = 90; + const mockContext = { + contextIndex: scrolledIndex, + setContextIndex: jest.fn(), + }; + + const { rerender, result } = renderHook( + ({ numItems }: { numItems: number }) => + useVirtualizer_unstable({ + numItems, + virtualizerLength, + itemSize: 100, + children: rowFunc, + containerSizeRef, + virtualizerContext: mockContext, + }), + { initialProps: { numItems: 100 } } + ); + + // Filter reduces the list from 100 → 10 items while actualIndex is still 90. + // Without the fix this throws: "RangeError: Invalid array length" + expect(() => rerender({ numItems: 10 })).not.toThrow(); + + // virtualizedChildren must be a valid (non-negative length) array + expect(result.current.virtualizedChildren.length).toBeGreaterThanOrEqual(0); + }); + it('should handle rapid re-renders without key conflicts', () => { const virtualizerLength = 8; const actualLength = 100;