diff --git a/Libraries/Lists/CellRenderMask.js b/Libraries/Lists/CellRenderMask.js index 66c667f4e31a76..fe4097f7c81f75 100644 --- a/Libraries/Lists/CellRenderMask.js +++ b/Libraries/Lists/CellRenderMask.js @@ -49,12 +49,18 @@ export class CellRenderMask { invariant( cells.first >= 0 && cells.first < this._numCells && - cells.last >= 0 && + cells.last >= -1 && cells.last < this._numCells && - cells.last >= cells.first, + cells.last >= cells.first - 1, 'CellRenderMask.addCells called with invalid cell range', ); + // VirtualizedList uses inclusive ranges, where zero-count states are + // possible. E.g. [0, -1] for no cells, starting at 0. + if (cells.last < cells.first) { + return; + } + const [firstIntersect, firstIntersectIdx] = this._findRegion(cells.first); const [lastIntersect, lastIntersectIdx] = this._findRegion(cells.last); diff --git a/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js b/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js index 7fa5ecde206371..56ed4486d8de94 100644 --- a/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js +++ b/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js @@ -779,9 +779,7 @@ class VirtualizedList extends React.PureComponent { if (itemCount > 0) { const allRegions = [cellsAroundViewport, ...(additionalRegions ?? [])]; for (const region of allRegions) { - if (region.last >= region.first) { - renderMask.addCells(region); - } + renderMask.addCells(region); } // The initially rendered cells are retained as part of the diff --git a/Libraries/Lists/__tests__/CellRenderMask-test.js b/Libraries/Lists/__tests__/CellRenderMask-test.js index bef5cc3fa3e31c..7230b7e76132ce 100644 --- a/Libraries/Lists/__tests__/CellRenderMask-test.js +++ b/Libraries/Lists/__tests__/CellRenderMask-test.js @@ -30,8 +30,8 @@ describe('CellRenderMask', () => { it('throws when adding invalid cell ranges', () => { const renderMask = new CellRenderMask(5); - expect(() => renderMask.addCells({first: -1, last: 0})).toThrow(); - expect(() => renderMask.addCells({first: 1, last: 0})).toThrow(); + expect(() => renderMask.addCells({first: -2, last: -1})).toThrow(); + expect(() => renderMask.addCells({first: -2, last: 0})).toThrow(); expect(() => renderMask.addCells({first: 0, last: 5})).toThrow(); expect(() => renderMask.addCells({first: 6, last: 7})).toThrow(); }); @@ -76,6 +76,15 @@ describe('CellRenderMask', () => { ]); }); + it('allows adding empty cell range', () => { + const renderMask = new CellRenderMask(5); + renderMask.addCells({first: 0, last: -1}); + + expect(renderMask.enumerateRegions()).toEqual([ + {first: 0, last: 4, isSpacer: true}, + ]); + }); + it('correctly replaces fragmented cell ranges', () => { const renderMask = new CellRenderMask(10); diff --git a/Libraries/Lists/__tests__/VirtualizedList-test.js b/Libraries/Lists/__tests__/VirtualizedList-test.js index 6eeb30212abb0c..8f802aaab1d455 100644 --- a/Libraries/Lists/__tests__/VirtualizedList-test.js +++ b/Libraries/Lists/__tests__/VirtualizedList-test.js @@ -736,6 +736,23 @@ it('renders offset cells in initial render when initialScrollIndex set', () => { expect(component).toMatchSnapshot(); }); +it('initially renders nothing when initialNumToRender is 0', () => { + const items = generateItems(10); + const ITEM_HEIGHT = 10; + + const component = ReactTestRenderer.create( + , + ); + + // Only a spacer should be present (a single item is present in the legacy + // implementation) + expect(component).toMatchSnapshot(); +}); + it('does not over-render when there is less than initialNumToRender cells', () => { const items = generateItems(10); const ITEM_HEIGHT = 10; diff --git a/Libraries/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap b/Libraries/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap index 8c96f5f9372b9b..c66d68042b8eab 100644 --- a/Libraries/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap +++ b/Libraries/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap @@ -2799,6 +2799,76 @@ exports[`gracefully handles negaitve initialScrollIndex 1`] = ` `; +exports[`initially renders nothing when initialNumToRender is 0 1`] = ` + + + + + + + + +`; + exports[`renders a zero-height tail spacer on initial render if getItemLayout not defined 1`] = ` `; +exports[`initially renders nothing when initialNumToRender is 0 1`] = ` + + + + + +`; + exports[`renders a zero-height tail spacer on initial render if getItemLayout not defined 1`] = `