Fix invariant violation when nesting VirtualizedList inside ListEmptyComponent (#35875)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/35875

Fixes https://github.com/facebook/react-native/issues/35871

Nested VirtualizedLists register to their parents for updates, associated to a specfific cellKey set by VirtualizedListCellContextProvider. This cellKey is usually set when rendering a cell for a data item, but we can also render a nested VirtualizedList by putting one in a ListHeaderComponent/ListFooterComponent/ListEmptyComponent.

D6603342 (https://github.com/facebook/react-native/commit/a010a0cebd4afc0d88336c2c265a5d9dbb19918f) added cellKeys when we render from a header/footer, but not ListEmptyComponent, so that association would silently fail earlier.

D39466677 (https://github.com/facebook/react-native/commit/010da67bef0c22418d0d41b7c2eae664672a4a27) added extra invariants to child list handling, that are now triggered by this case, complaining because we are trying to unregister a child list we never successfully registered, due to a missing cellKey.

This fixes the issue by providing a cellKey for ListEmptyComponent as well.

Changelog:
[General][Fixed] - Fix invariant violation when nesting VirtualizedList inside ListEmptyComponent

Reviewed By: christophpurrer

Differential Revision: D42574462

fbshipit-source-id: f76fa795bf471cb8a929c2efdbd814ea51927663
This commit is contained in:
Nick Gerleman
2023-01-19 05:13:50 -08:00
committed by Facebook GitHub Bot
parent 4cdc2c48e8
commit 1fef376812
2 changed files with 39 additions and 10 deletions
+13 -10
View File
@@ -871,16 +871,19 @@ export default class VirtualizedList extends StateSafePureComponent<
<ListEmptyComponent />
)): any);
cells.push(
React.cloneElement(element, {
key: '$empty',
onLayout: (event: LayoutEvent) => {
this._onLayoutEmpty(event);
if (element.props.onLayout) {
element.props.onLayout(event);
}
},
style: StyleSheet.compose(inversionStyle, element.props.style),
}),
<VirtualizedListCellContextProvider
cellKey={this._getCellKey() + '-empty'}
key="$empty">
{React.cloneElement(element, {
onLayout: (event: LayoutEvent) => {
this._onLayoutEmpty(event);
if (element.props.onLayout) {
element.props.onLayout(event);
}
},
style: StyleSheet.compose(inversionStyle, element.props.style),
})}
</VirtualizedListCellContextProvider>,
);
}
@@ -245,6 +245,32 @@ describe('VirtualizedList', () => {
expect(component).toMatchSnapshot();
});
it('handles nested list in ListEmptyComponent', () => {
const ListEmptyComponent = (
<VirtualizedList {...baseItemProps(generateItems(1))} />
);
let component;
ReactTestRenderer.act(() => {
component = ReactTestRenderer.create(
<VirtualizedList
{...baseItemProps([])}
ListEmptyComponent={ListEmptyComponent}
/>,
);
});
ReactTestRenderer.act(() => {
component.update(
<VirtualizedList
{...baseItemProps(generateItems(5))}
ListEmptyComponent={ListEmptyComponent}
/>,
);
});
});
it('returns the viewableItems correctly in the onViewableItemsChanged callback after changing the data', () => {
const ITEM_HEIGHT = 800;
let data = [{key: 'i1'}, {key: 'i2'}, {key: 'i3'}];