From 010da67bef0c22418d0d41b7c2eae664672a4a27 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Mon, 19 Sep 2022 19:25:09 -0700 Subject: [PATCH] Remove dependence on VirtualizedList `listKey` prop Summary: Following up on the previous diff to remove the only usage of `listKey` for persistent association, we can remove the need for a manual `listKey`, and instead rely on per-instance association via refs. A followup change will remove the existing usages. Changelog: [General][Removed] - Remove VirtualizedList `listKey` prop Reviewed By: p-sun Differential Revision: D39466677 fbshipit-source-id: 6b49f45c987fff9836918ba833fbb16f24414ff8 --- Libraries/Lists/ChildListCollection.js | 72 +++++++++ Libraries/Lists/VirtualizedList.js | 143 ++++-------------- Libraries/Lists/VirtualizedListContext.js | 29 +--- Libraries/Lists/VirtualizedListProps.js | 2 + .../Lists/VirtualizedList_EXPERIMENTAL.js | 139 ++++------------- .../Lists/__tests__/VirtualizedList-test.js | 68 --------- 6 files changed, 129 insertions(+), 324 deletions(-) create mode 100644 Libraries/Lists/ChildListCollection.js diff --git a/Libraries/Lists/ChildListCollection.js b/Libraries/Lists/ChildListCollection.js new file mode 100644 index 00000000000000..9c12031f6a1978 --- /dev/null +++ b/Libraries/Lists/ChildListCollection.js @@ -0,0 +1,72 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +import invariant from 'invariant'; + +export default class ChildListCollection { + _cellKeyToChildren: Map> = new Map(); + _childrenToCellKey: Map = new Map(); + + add(list: TList, cellKey: string): void { + invariant( + !this._childrenToCellKey.has(list), + 'Trying to add already present child list', + ); + + const cellLists = this._cellKeyToChildren.get(cellKey) ?? new Set(); + cellLists.add(list); + this._cellKeyToChildren.set(cellKey, cellLists); + + this._childrenToCellKey.set(list, cellKey); + } + + remove(list: TList): void { + const cellKey = this._childrenToCellKey.get(list); + invariant(cellKey != null, 'Trying to remove non-present child list'); + this._childrenToCellKey.delete(list); + + const cellLists = this._cellKeyToChildren.get(cellKey); + invariant(cellLists, '_cellKeyToChildren should contain cellKey'); + cellLists.delete(list); + + if (cellLists.size === 0) { + this._cellKeyToChildren.delete(cellKey); + } + } + + forEach(fn: TList => void): void { + for (const listSet of this._cellKeyToChildren.values()) { + for (const list of listSet) { + fn(list); + } + } + } + + forEachInCell(cellKey: string, fn: TList => void): void { + const listSet = this._cellKeyToChildren.get(cellKey) ?? []; + for (const list of listSet) { + fn(list); + } + } + + anyInCell(cellKey: string, fn: TList => boolean): boolean { + const listSet = this._cellKeyToChildren.get(cellKey) ?? []; + for (const list of listSet) { + if (fn(list)) { + return true; + } + } + return false; + } + + size(): number { + return this._childrenToCellKey.size; + } +} diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index eed4d6f36075cd..2881a952991b6f 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -24,7 +24,6 @@ import type { export type {RenderItemProps, RenderItemType, Separators}; import { - type ListDebugInfo, VirtualizedListCellContextProvider, VirtualizedListContext, VirtualizedListContextProvider, @@ -35,6 +34,7 @@ import { } from './VirtualizeUtils'; import * as VirtualizedListInjection from './VirtualizedListInjection'; import * as React from 'react'; +import ChildListCollection from './ChildListCollection'; const RefreshControl = require('../Components/RefreshControl/RefreshControl'); const ScrollView = require('../Components/ScrollView/ScrollView'); @@ -294,7 +294,7 @@ class VirtualizedList extends React.PureComponent { recordInteraction() { this._nestedChildLists.forEach(childList => { - childList && childList.recordInteraction(); + childList.recordInteraction(); }); this._viewabilityTuples.forEach(t => { t.viewabilityHelper.recordInteraction(); @@ -349,19 +349,6 @@ class VirtualizedList extends React.PureComponent { return this.context?.cellKey || 'rootList'; } - _getListKey(): string { - return this.props.listKey || this._getCellKey(); - } - - _getDebugInfo(): ListDebugInfo { - return { - listKey: this._getListKey(), - cellKey: this._getCellKey(), - horizontal: horizontalOrDefault(this.props.horizontal), - parent: this.context?.debugInfo, - }; - } - // $FlowFixMe[missing-local-annot] _getScrollMetrics = () => { return this._scrollMetrics; @@ -382,41 +369,20 @@ class VirtualizedList extends React.PureComponent { _registerAsNestedChild = (childList: { cellKey: string, - key: string, ref: React.ElementRef, - parentDebugInfo: ListDebugInfo, - ... }): void => { - const specificRef = ((childList.ref: any): VirtualizedList); - // Register the mapping between this child key and the cellKey for its cell - const childListsInCell = - this._cellKeysToChildListKeys.get(childList.cellKey) || new Set(); - childListsInCell.add(childList.key); - this._cellKeysToChildListKeys.set(childList.cellKey, childListsInCell); - const existingChildData = this._nestedChildLists.get(childList.key); - if (existingChildData) { - console.error( - 'A VirtualizedList contains a cell which itself contains ' + - 'more than one VirtualizedList of the same orientation as the parent ' + - 'list. You must pass a unique listKey prop to each sibling list.\n\n' + - describeNestedLists({ - ...childList, - ref: specificRef, - // We're called from the child's componentDidMount, so it's safe to - // read the child's props here (albeit weird). - horizontal: !!specificRef.props.horizontal, - }), - ); - } - this._nestedChildLists.set(childList.key, specificRef); - + const listRef = ((childList.ref: any): VirtualizedList); + this._nestedChildLists.add(listRef, childList.cellKey); if (this._hasInteracted) { - specificRef.recordInteraction(); + listRef.recordInteraction(); } }; - _unregisterAsNestedChild = (childList: {key: string, ...}): void => { - this._nestedChildLists.delete(childList.key); + _unregisterAsNestedChild = (childList: { + ref: React.ElementRef, + }): void => { + const listRef = ((childList.ref: any): VirtualizedList); + this._nestedChildLists.remove(listRef); }; state: State; @@ -476,28 +442,15 @@ class VirtualizedList extends React.PureComponent { componentDidMount() { if (this._isNestedWithSameOrientation()) { this.context.registerAsNestedChild({ - cellKey: this._getCellKey(), - key: this._getListKey(), ref: this, - // NOTE: When the child mounts (here) it's not necessarily safe to read - // the parent's props. This is why we explicitly propagate debugInfo - // "down" via context and "up" again via this method call on the - // parent. - parentDebugInfo: this.context.debugInfo, + cellKey: this.context.cellKey, }); } } componentWillUnmount() { if (this._isNestedWithSameOrientation()) { - this.context.unregisterAsNestedChild({ - key: this._getListKey(), - state: { - first: this.state.first, - last: this.state.last, - frames: this._frames, - }, - }); + this.context.unregisterAsNestedChild({ref: this}); } this._updateViewableItems(null); this._updateCellsToRenderBatcher.dispose({abort: true}); @@ -851,7 +804,6 @@ class VirtualizedList extends React.PureComponent { getOutermostParentListRef: this._getOutermostParentListRef, registerAsNestedChild: this._registerAsNestedChild, unregisterAsNestedChild: this._unregisterAsNestedChild, - debugInfo: this._getDebugInfo(), }}> {React.cloneElement( ( @@ -927,8 +879,6 @@ class VirtualizedList extends React.PureComponent { } _averageCellLength = 0; - // Maps a cell key to the set of keys for all outermost child lists within that cell - _cellKeysToChildListKeys: Map> = new Map(); _cellRefs: {[string]: null | CellRenderer} = {}; _fillRateHelper: FillRateHelper; _frames: { @@ -949,7 +899,8 @@ class VirtualizedList extends React.PureComponent { _hiPriInProgress: boolean = false; // flag to prevent infinite hiPri cell limit update _highestMeasuredFrameIndex = 0; _indicesToKeys: Map = new Map(); - _nestedChildLists: Map = new Map(); + _nestedChildLists: ChildListCollection = + new ChildListCollection(); _offsetFromParentVirtualizedList: number = 0; _prevParentOffset: number = 0; // $FlowFixMe[missing-local-annot] @@ -1067,13 +1018,9 @@ class VirtualizedList extends React.PureComponent { }; _triggerRemeasureForChildListsInCell(cellKey: string): void { - const childListKeys = this._cellKeysToChildListKeys.get(cellKey); - if (childListKeys) { - for (let childKey of childListKeys) { - const childList = this._nestedChildLists.get(childKey); - childList && childList.measureLayoutRelativeToContainingList(); - } - } + this._nestedChildLists.forEachInCell(cellKey, childList => { + childList.measureLayoutRelativeToContainingList(); + }); } measureLayoutRelativeToContainingList(): void { @@ -1107,14 +1054,8 @@ class VirtualizedList extends React.PureComponent { // If metrics of the scrollView changed, then we triggered remeasure for child list // to ensure VirtualizedList has the right information. - this._cellKeysToChildListKeys.forEach(childListKeys => { - if (childListKeys) { - for (let childKey of childListKeys) { - const childList = this._nestedChildLists.get(childKey); - childList && - childList.measureLayoutRelativeToContainingList(); - } - } + this._nestedChildLists.forEach(childList => { + childList.measureLayoutRelativeToContainingList(); }); } }, @@ -1547,7 +1488,7 @@ class VirtualizedList extends React.PureComponent { last: Math.min(state.last + renderAhead, getItemCount(data) - 1), }; } - if (newState && this._nestedChildLists.size > 0) { + if (newState && this._nestedChildLists.size() > 0) { const newFirst = newState.first; const newLast = newState.last; // If some cell in the new state has a child list in it, we should only render @@ -1556,21 +1497,16 @@ class VirtualizedList extends React.PureComponent { // their items. for (let ii = newFirst; ii <= newLast; ii++) { const cellKeyForIndex = this._indicesToKeys.get(ii); - const childListKeys = - cellKeyForIndex && - this._cellKeysToChildListKeys.get(cellKeyForIndex); - if (!childListKeys) { + if (cellKeyForIndex == null) { continue; } - let someChildHasMore = false; // For each cell, need to check whether any child list in it has more elements to render - for (let childKey of childListKeys) { - const childList = this._nestedChildLists.get(childKey); - if (childList && childList.hasMore()) { - someChildHasMore = true; - break; - } - } + + const someChildHasMore = this._nestedChildLists.anyInCell( + cellKeyForIndex, + childList => childList.hasMore(), + ); + if (someChildHasMore) { newState.last = ii; break; @@ -1879,31 +1815,6 @@ class CellRenderer extends React.Component< } } -function describeNestedLists(childList: { - +cellKey: string, - +key: string, - +ref: VirtualizedList, - +parentDebugInfo: ListDebugInfo, - +horizontal: boolean, - ... -}) { - let trace = - 'VirtualizedList trace:\n' + - ` Child (${childList.horizontal ? 'horizontal' : 'vertical'}):\n` + - ` listKey: ${childList.key}\n` + - ` cellKey: ${childList.cellKey}`; - - let debugInfo: ?ListDebugInfo = childList.parentDebugInfo; - while (debugInfo) { - trace += - `\n Parent (${debugInfo.horizontal ? 'horizontal' : 'vertical'}):\n` + - ` listKey: ${debugInfo.listKey}\n` + - ` cellKey: ${debugInfo.cellKey}`; - debugInfo = debugInfo.parent; - } - return trace; -} - const styles = StyleSheet.create({ verticallyInverted: { transform: [{scaleY: -1}], diff --git a/Libraries/Lists/VirtualizedListContext.js b/Libraries/Lists/VirtualizedListContext.js index 93ebd22f7eaf84..c5dda0733a3962 100644 --- a/Libraries/Lists/VirtualizedListContext.js +++ b/Libraries/Lists/VirtualizedListContext.js @@ -4,25 +4,13 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict-local + * @flow strict * @format */ import * as React from 'react'; import {useMemo, useContext} from 'react'; -// Data propagated through nested lists (regardless of orientation) that is -// useful for producing diagnostics for usage errors involving nesting (e.g -// missing/duplicate keys). -export type ListDebugInfo = $ReadOnly<{ - cellKey: string, - listKey: string, - parent: ?ListDebugInfo, - // We include all ancestors regardless of orientation, so this is not always - // identical to the child's orientation. - horizontal: boolean, -}>; - type Context = $ReadOnly<{ cellKey: ?string, getScrollMetrics: () => { @@ -39,14 +27,11 @@ type Context = $ReadOnly<{ getOutermostParentListRef: () => React.ElementRef, registerAsNestedChild: ({ cellKey: string, - key: string, ref: React.ElementRef, - parentDebugInfo: ListDebugInfo, }) => void, unregisterAsNestedChild: ({ - key: string, + ref: React.ElementRef, }) => void, - debugInfo: ListDebugInfo, }>; export const VirtualizedListContext: React.Context = @@ -89,12 +74,6 @@ export function VirtualizedListContextProvider({ getOutermostParentListRef: value.getOutermostParentListRef, registerAsNestedChild: value.registerAsNestedChild, unregisterAsNestedChild: value.unregisterAsNestedChild, - debugInfo: { - cellKey: value.debugInfo.cellKey, - horizontal: value.debugInfo.horizontal, - listKey: value.debugInfo.listKey, - parent: value.debugInfo.parent, - }, }), [ value.getScrollMetrics, @@ -102,10 +81,6 @@ export function VirtualizedListContextProvider({ value.getOutermostParentListRef, value.registerAsNestedChild, value.unregisterAsNestedChild, - value.debugInfo.cellKey, - value.debugInfo.horizontal, - value.debugInfo.listKey, - value.debugInfo.parent, ], ); return ( diff --git a/Libraries/Lists/VirtualizedListProps.js b/Libraries/Lists/VirtualizedListProps.js index 7bf0cd0f539778..8410a8d569f21e 100644 --- a/Libraries/Lists/VirtualizedListProps.js +++ b/Libraries/Lists/VirtualizedListProps.js @@ -167,6 +167,8 @@ type OptionalProps = {| * A unique identifier for this list. If there are multiple VirtualizedLists at the same level of * nesting within another VirtualizedList, this key is necessary for virtualization to * work properly. + * + * @deprecated no longer used/required */ listKey?: string, /** diff --git a/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js b/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js index b766af6e20c870..d5b1de4f070e92 100644 --- a/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js +++ b/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js @@ -29,7 +29,6 @@ import type { export type {RenderItemProps, RenderItemType, Separators}; import { - type ListDebugInfo, VirtualizedListCellContextProvider, VirtualizedListContext, VirtualizedListContextProvider, @@ -43,6 +42,7 @@ import * as React from 'react'; import {CellRenderMask} from './CellRenderMask'; import clamp from '../Utilities/clamp'; import StateSafePureComponent from './StateSafePureComponent'; +import ChildListCollection from './ChildListCollection'; const RefreshControl = require('../Components/RefreshControl/RefreshControl'); const ScrollView = require('../Components/ScrollView/ScrollView'); @@ -370,19 +370,6 @@ class VirtualizedList extends StateSafePureComponent { return this.context?.cellKey || 'rootList'; } - _getListKey(): string { - return this.props.listKey || this._getCellKey(); - } - - _getDebugInfo(): ListDebugInfo { - return { - listKey: this._getListKey(), - cellKey: this._getCellKey(), - horizontal: horizontalOrDefault(this.props.horizontal), - parent: this.context?.debugInfo, - }; - } - // $FlowFixMe[missing-local-annot] _getScrollMetrics = () => { return this._scrollMetrics; @@ -403,41 +390,20 @@ class VirtualizedList extends StateSafePureComponent { _registerAsNestedChild = (childList: { cellKey: string, - key: string, ref: React.ElementRef, - parentDebugInfo: ListDebugInfo, - ... }): void => { - const specificRef = ((childList.ref: any): VirtualizedList); - // Register the mapping between this child key and the cellKey for its cell - const childListsInCell = - this._cellKeysToChildListKeys.get(childList.cellKey) || new Set(); - childListsInCell.add(childList.key); - this._cellKeysToChildListKeys.set(childList.cellKey, childListsInCell); - const existingChildData = this._nestedChildLists.get(childList.key); - if (existingChildData) { - console.error( - 'A VirtualizedList contains a cell which itself contains ' + - 'more than one VirtualizedList of the same orientation as the parent ' + - 'list. You must pass a unique listKey prop to each sibling list.\n\n' + - describeNestedLists({ - ...childList, - ref: specificRef, - // We're called from the child's componentDidMount, so it's safe to - // read the child's props here (albeit weird). - horizontal: !!specificRef.props.horizontal, - }), - ); - } - this._nestedChildLists.set(childList.key, specificRef); - + const listRef = ((childList.ref: any): VirtualizedList); + this._nestedChildLists.add(listRef, childList.cellKey); if (this._hasInteracted) { - specificRef.recordInteraction(); + listRef.recordInteraction(); } }; - _unregisterAsNestedChild = (childList: {key: string, ...}): void => { - this._nestedChildLists.delete(childList.key); + _unregisterAsNestedChild = (childList: { + ref: React.ElementRef, + }): void => { + const listRef = ((childList.ref: any): VirtualizedList); + this._nestedChildLists.remove(listRef); }; state: State; @@ -640,7 +606,7 @@ class VirtualizedList extends StateSafePureComponent { ); } - if (this._nestedChildLists.size > 0) { + if (this._nestedChildLists.size() > 0) { // If some cell in the new state has a child list in it, we should only render // up through that item, so that we give that list a chance to render. // Otherwise there's churn from multiple child lists mounting and un-mounting @@ -661,17 +627,13 @@ class VirtualizedList extends StateSafePureComponent { _findFirstChildWithMore(first: number, last: number): number | null { for (let ii = first; ii <= last; ii++) { const cellKeyForIndex = this._indicesToKeys.get(ii); - const childListKeys = - cellKeyForIndex && this._cellKeysToChildListKeys.get(cellKeyForIndex); - if (!childListKeys) { - continue; - } - // For each cell, need to check whether any child list in it has more elements to render - for (let childKey of childListKeys) { - const childList = this._nestedChildLists.get(childKey); - if (childList && childList.hasMore()) { - return ii; - } + if ( + cellKeyForIndex != null && + this._nestedChildLists.anyInCell(cellKeyForIndex, childList => + childList.hasMore(), + ) + ) { + return ii; } } @@ -681,27 +643,15 @@ class VirtualizedList extends StateSafePureComponent { componentDidMount() { if (this._isNestedWithSameOrientation()) { this.context.registerAsNestedChild({ - cellKey: this._getCellKey(), - key: this._getListKey(), ref: this, - // NOTE: When the child mounts (here) it's not necessarily safe to read - // the parent's props. This is why we explicitly propagate debugInfo - // "down" via context and "up" again via this method call on the - // parent. - parentDebugInfo: this.context.debugInfo, + cellKey: this.context.cellKey, }); } } componentWillUnmount() { if (this._isNestedWithSameOrientation()) { - this.context.unregisterAsNestedChild({ - key: this._getListKey(), - state: { - ...this.state, - frames: this._frames, - }, - }); + this.context.unregisterAsNestedChild({ref: this}); } this._updateCellsToRenderBatcher.dispose({abort: true}); this._viewabilityTuples.forEach(tuple => { @@ -1054,7 +1004,6 @@ class VirtualizedList extends StateSafePureComponent { getOutermostParentListRef: this._getOutermostParentListRef, registerAsNestedChild: this._registerAsNestedChild, unregisterAsNestedChild: this._unregisterAsNestedChild, - debugInfo: this._getDebugInfo(), }}> {React.cloneElement( ( @@ -1131,8 +1080,6 @@ class VirtualizedList extends StateSafePureComponent { } _averageCellLength = 0; - // Maps a cell key to the set of keys for all outermost child lists within that cell - _cellKeysToChildListKeys: Map> = new Map(); _cellRefs: {[string]: null | CellRenderer} = {}; _fillRateHelper: FillRateHelper; _frames: { @@ -1154,7 +1101,8 @@ class VirtualizedList extends StateSafePureComponent { _highestMeasuredFrameIndex = 0; _indicesToKeys: Map = new Map(); _lastFocusedCellKey: ?string = null; - _nestedChildLists: Map = new Map(); + _nestedChildLists: ChildListCollection = + new ChildListCollection(); _offsetFromParentVirtualizedList: number = 0; _prevParentOffset: number = 0; // $FlowFixMe[missing-local-annot] @@ -1288,13 +1236,9 @@ class VirtualizedList extends StateSafePureComponent { }; _triggerRemeasureForChildListsInCell(cellKey: string): void { - const childListKeys = this._cellKeysToChildListKeys.get(cellKey); - if (childListKeys) { - for (let childKey of childListKeys) { - const childList = this._nestedChildLists.get(childKey); - childList && childList.measureLayoutRelativeToContainingList(); - } - } + this._nestedChildLists.forEachInCell(cellKey, childList => { + childList.measureLayoutRelativeToContainingList(); + }); } measureLayoutRelativeToContainingList(): void { @@ -1328,14 +1272,8 @@ class VirtualizedList extends StateSafePureComponent { // If metrics of the scrollView changed, then we triggered remeasure for child list // to ensure VirtualizedList has the right information. - this._cellKeysToChildListKeys.forEach(childListKeys => { - if (childListKeys) { - for (let childKey of childListKeys) { - const childList = this._nestedChildLists.get(childKey); - childList && - childList.measureLayoutRelativeToContainingList(); - } - } + this._nestedChildLists.forEach(childList => { + childList.measureLayoutRelativeToContainingList(); }); } }, @@ -2128,31 +2066,6 @@ class CellRenderer extends React.Component< } } -function describeNestedLists(childList: { - +cellKey: string, - +key: string, - +ref: VirtualizedList, - +parentDebugInfo: ListDebugInfo, - +horizontal: boolean, - ... -}) { - let trace = - 'VirtualizedList trace:\n' + - ` Child (${childList.horizontal ? 'horizontal' : 'vertical'}):\n` + - ` listKey: ${childList.key}\n` + - ` cellKey: ${childList.cellKey}`; - - let debugInfo: ?ListDebugInfo = childList.parentDebugInfo; - while (debugInfo) { - trace += - `\n Parent (${debugInfo.horizontal ? 'horizontal' : 'vertical'}):\n` + - ` listKey: ${debugInfo.listKey}\n` + - ` cellKey: ${debugInfo.cellKey}`; - debugInfo = debugInfo.parent; - } - return trace; -} - const styles = StyleSheet.create({ verticallyInverted: { transform: [{scaleY: -1}], diff --git a/Libraries/Lists/__tests__/VirtualizedList-test.js b/Libraries/Lists/__tests__/VirtualizedList-test.js index 4b87a333c5700f..d7207c15fa7d6f 100644 --- a/Libraries/Lists/__tests__/VirtualizedList-test.js +++ b/Libraries/Lists/__tests__/VirtualizedList-test.js @@ -420,74 +420,6 @@ describe('VirtualizedList', () => { expect(onEndReached).toHaveBeenCalled(); }); - it('provides a trace when a listKey collision occurs', () => { - const errors = []; - jest.spyOn(console, 'error').mockImplementation((...args) => { - // Silence the DEV-only React error boundary warning. - if ((args[0] || '').startsWith('The above error occurred in the ')) { - return; - } - errors.push(args); - }); - const commonProps = { - data: [{key: 'cell0'}], - getItem: (data, index) => data[index], - getItemCount: data => data.length, - renderItem: ({item}) => , - }; - try { - ReactTestRenderer.create( - ( - ( - <> - {/* Force a collision */} - - - - )} - /> - )} - />, - ); - expect(errors).toMatchInlineSnapshot(` - Array [ - Array [ - "A VirtualizedList contains a cell which itself contains more than one VirtualizedList of the same orientation as the parent list. You must pass a unique listKey prop to each sibling list. - - VirtualizedList trace: - Child (horizontal): - listKey: level2 - cellKey: cell0 - Parent (horizontal): - listKey: level1 - cellKey: cell0 - Parent (vertical): - listKey: level0 - cellKey: rootList", - ], - ] - `); - } finally { - console.error.mockRestore(); - } - }); - it('throws if using scrollToIndex with index less than 0', () => { const component = ReactTestRenderer.create(