Skip to content

Commit 1f5f284

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Fix invariant violation when nesting VirtualizedList inside ListEmptyComponent
Summary: Fixes #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 (a010a0c) added cellKeys when we render from a header/footer, but not ListEmptyComponent, so that association would silently fail earlier. D39466677 (010da67) 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. It also cleans up some of the parameterization needed from when we had two VirtualizedList implementations. Changelog: [General][Fixed] - Fix invariant violation when nesting VirtualizedList inside ListEmptyComponent Differential Revision: D42574462 fbshipit-source-id: 4d7817fa6298ca1ef1b0c5977f19f6b68123db8b
1 parent 473eb1d commit 1f5f284

File tree

3 files changed

+53
-18
lines changed

3 files changed

+53
-18
lines changed

Libraries/Lists/ChildListCollection.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
* @format
99
*/
1010

11+
import type VirtualizedList from './VirtualizedList';
12+
1113
import invariant from 'invariant';
1214

13-
export default class ChildListCollection<TList> {
14-
_cellKeyToChildren: Map<string, Set<TList>> = new Map();
15-
_childrenToCellKey: Map<TList, string> = new Map();
15+
export default class ChildListCollection {
16+
_cellKeyToChildren: Map<string, Set<VirtualizedList>> = new Map();
17+
_childrenToCellKey: Map<VirtualizedList, string> = new Map();
1618

17-
add(list: TList, cellKey: string): void {
19+
add(list: VirtualizedList, cellKey: string): void {
1820
invariant(
1921
!this._childrenToCellKey.has(list),
2022
'Trying to add already present child list',
@@ -27,7 +29,7 @@ export default class ChildListCollection<TList> {
2729
this._childrenToCellKey.set(list, cellKey);
2830
}
2931

30-
remove(list: TList): void {
32+
remove(list: VirtualizedList): void {
3133
const cellKey = this._childrenToCellKey.get(list);
3234
invariant(cellKey != null, 'Trying to remove non-present child list');
3335
this._childrenToCellKey.delete(list);
@@ -41,22 +43,22 @@ export default class ChildListCollection<TList> {
4143
}
4244
}
4345

44-
forEach(fn: TList => void): void {
46+
forEach(fn: VirtualizedList => void): void {
4547
for (const listSet of this._cellKeyToChildren.values()) {
4648
for (const list of listSet) {
4749
fn(list);
4850
}
4951
}
5052
}
5153

52-
forEachInCell(cellKey: string, fn: TList => void): void {
54+
forEachInCell(cellKey: string, fn: VirtualizedList => void): void {
5355
const listSet = this._cellKeyToChildren.get(cellKey) ?? [];
5456
for (const list of listSet) {
5557
fn(list);
5658
}
5759
}
5860

59-
anyInCell(cellKey: string, fn: TList => boolean): boolean {
61+
anyInCell(cellKey: string, fn: VirtualizedList => boolean): boolean {
6062
const listSet = this._cellKeyToChildren.get(cellKey) ?? [];
6163
for (const list of listSet) {
6264
if (fn(list)) {

Libraries/Lists/VirtualizedList.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,10 @@ export default class VirtualizedList extends StateSafePureComponent<
648648

649649
componentDidMount() {
650650
if (this._isNestedWithSameOrientation()) {
651+
invariant(
652+
this.context.cellKey != null,
653+
'A cellkey should be set for nested children',
654+
);
651655
this.context.registerAsNestedChild({
652656
ref: this,
653657
cellKey: this.context.cellKey,
@@ -871,16 +875,19 @@ export default class VirtualizedList extends StateSafePureComponent<
871875
<ListEmptyComponent />
872876
)): any);
873877
cells.push(
874-
React.cloneElement(element, {
875-
key: '$empty',
876-
onLayout: (event: LayoutEvent) => {
877-
this._onLayoutEmpty(event);
878-
if (element.props.onLayout) {
879-
element.props.onLayout(event);
880-
}
881-
},
882-
style: StyleSheet.compose(inversionStyle, element.props.style),
883-
}),
878+
<VirtualizedListCellContextProvider
879+
cellKey={this._getCellKey() + '-empty'}
880+
key="$empty">
881+
{React.cloneElement(element, {
882+
onLayout: (event: LayoutEvent) => {
883+
this._onLayoutEmpty(event);
884+
if (element.props.onLayout) {
885+
element.props.onLayout(event);
886+
}
887+
},
888+
style: StyleSheet.compose(inversionStyle, element.props.style),
889+
})}
890+
</VirtualizedListCellContextProvider>,
884891
);
885892
}
886893

Libraries/Lists/__tests__/VirtualizedList-test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,32 @@ describe('VirtualizedList', () => {
245245
expect(component).toMatchSnapshot();
246246
});
247247

248+
it('handles nested list in ListEmptyComponent', () => {
249+
const ListEmptyComponent = (
250+
<VirtualizedList {...baseItemProps(generateItems(1))} />
251+
);
252+
253+
let component;
254+
255+
ReactTestRenderer.act(() => {
256+
component = ReactTestRenderer.create(
257+
<VirtualizedList
258+
{...baseItemProps([])}
259+
ListEmptyComponent={ListEmptyComponent}
260+
/>,
261+
);
262+
});
263+
264+
ReactTestRenderer.act(() => {
265+
component.update(
266+
<VirtualizedList
267+
{...baseItemProps(generateItems(5))}
268+
ListEmptyComponent={ListEmptyComponent}
269+
/>,
270+
);
271+
});
272+
});
273+
248274
it('returns the viewableItems correctly in the onViewableItemsChanged callback after changing the data', () => {
249275
const ITEM_HEIGHT = 800;
250276
let data = [{key: 'i1'}, {key: 'i2'}, {key: 'i3'}];

0 commit comments

Comments
 (0)