From 184b558a4ab3e1d4a02bacfb1f628a8751dbf7db Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Fri, 28 Jul 2023 12:55:17 -0700 Subject: [PATCH] Fix invariant violation when `maintainVisibleContentPosition` adjustment moves window before list start (#38655) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38655 https://github.com/facebook/react-native/pull/35993 added logic in VirtualizedList to support `maintainVisibleContentPosition`. This logic makes sure that a previously visible cell being used as an anchor remains rendered after new content is added. The strategy here is to calculate the difference in previous and new positions of the anchor, and move the render window to its new location during item change. `minIndexForVisible` is used as this anchor. When an item change moves the anchor to a position below `minIndexForVisible`, shifting the render window may result in a window which starts before zero. This fixes up `_constrainToItemCount()` to handle this. Changelog: [General][Fixed] - Fix invariant violation when `maintainVisibleContentPosition` adjustment moves window before list start Reviewed By: yungsters Differential Revision: D47846165 fbshipit-source-id: 8a36f66fdad321acb255745dad85618d28c54dba --- .../Lists/VirtualizedList.js | 10 +- .../Lists/__tests__/VirtualizedList-test.js | 47 +++ .../VirtualizedList-test.js.snap | 267 ++++++++++++++++++ 3 files changed, 321 insertions(+), 3 deletions(-) diff --git a/packages/virtualized-lists/Lists/VirtualizedList.js b/packages/virtualized-lists/Lists/VirtualizedList.js index 8a23e81cbef1a6..291dbb660823f2 100644 --- a/packages/virtualized-lists/Lists/VirtualizedList.js +++ b/packages/virtualized-lists/Lists/VirtualizedList.js @@ -881,15 +881,19 @@ class VirtualizedList extends StateSafePureComponent { props: Props, ): {first: number, last: number} { const itemCount = props.getItemCount(props.data); - const last = Math.min(itemCount - 1, cells.last); + const lastPossibleCellIndex = itemCount - 1; + // Constraining `last` may significantly shrink the window. Adjust `first` + // to expand the window if the new `last` results in a new window smaller + // than the number of cells rendered per batch. const maxToRenderPerBatch = maxToRenderPerBatchOrDefault( props.maxToRenderPerBatch, ); + const maxFirst = Math.max(0, lastPossibleCellIndex - maxToRenderPerBatch); return { - first: clamp(0, itemCount - 1 - maxToRenderPerBatch, cells.first), - last, + first: clamp(0, cells.first, maxFirst), + last: Math.min(lastPossibleCellIndex, cells.last), }; } diff --git a/packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js b/packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js index c030e8a2d7e690..774a2473556bc1 100644 --- a/packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js +++ b/packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js @@ -2224,6 +2224,53 @@ it('handles maintainVisibleContentPosition', () => { expect(component).toMatchSnapshot(); }); +it('handles maintainVisibleContentPosition when anchor moves before minIndexForVisible', () => { + const items = generateItems(20); + const ITEM_HEIGHT = 10; + + // Render a list with `minIndexForVisible: 1` + let component; + ReactTestRenderer.act(() => { + component = ReactTestRenderer.create( + , + ); + }); + + ReactTestRenderer.act(() => { + simulateLayout(component, { + viewport: {width: 10, height: 50}, + content: {width: 10, height: items.length * ITEM_HEIGHT}, + }); + + performAllBatches(); + }); + + expect(component).toMatchSnapshot(); + + // Remove the first item to shift the previous anchor to be before + // `minIndexForVisible`. + const [, ...restItems] = items; + ReactTestRenderer.act(() => { + component.update( + , + ); + }); + + expect(component).toMatchSnapshot(); +}); + function generateItems(count, startKey = 0) { return Array(count) .fill() diff --git a/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap b/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap index b877e3da84d999..5e9f7f1adaeb70 100644 --- a/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap +++ b/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap @@ -3658,6 +3658,273 @@ exports[`handles maintainVisibleContentPosition 3`] = ` `; +exports[`handles maintainVisibleContentPosition when anchor moves before minIndexForVisible 1`] = ` + + + + + + + + + + + + + + + + + + + + +`; + +exports[`handles maintainVisibleContentPosition when anchor moves before minIndexForVisible 2`] = ` + + + + + + + + + + + + + + + + + +`; + exports[`initially renders nothing when initialNumToRender is 0 1`] = `