mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Merge pull request #37258 from bang9/cherrypick-0.72-fix-virtualized-list-for-mvcp
This commit is contained in:
@@ -111,6 +111,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
|
||||
<RCTRefreshControl />
|
||||
<View>
|
||||
<View
|
||||
collapsable={false}
|
||||
onLayout={[Function]}
|
||||
>
|
||||
<header />
|
||||
|
||||
@@ -243,6 +243,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
<RCTRefreshControl />
|
||||
<View>
|
||||
<View
|
||||
collapsable={false}
|
||||
onLayout={[Function]}
|
||||
>
|
||||
<header
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
const React = require('react');
|
||||
|
||||
const {
|
||||
ActivityIndicator,
|
||||
Animated,
|
||||
Image,
|
||||
Platform,
|
||||
@@ -33,16 +34,28 @@ export type Item = {
|
||||
...
|
||||
};
|
||||
|
||||
function genItemData(count: number, start: number = 0): Array<Item> {
|
||||
function genItemData(i: number): Item {
|
||||
const itemHash = Math.abs(hashCode('Item ' + i));
|
||||
return {
|
||||
title: 'Item ' + i,
|
||||
text: LOREM_IPSUM.substr(0, (itemHash % 301) + 20),
|
||||
key: String(i),
|
||||
pressed: false,
|
||||
};
|
||||
}
|
||||
|
||||
function genNewerItems(count: number, start: number = 0): Array<Item> {
|
||||
const dataBlob = [];
|
||||
for (let ii = start; ii < count + start; ii++) {
|
||||
const itemHash = Math.abs(hashCode('Item ' + ii));
|
||||
dataBlob.push({
|
||||
title: 'Item ' + ii,
|
||||
text: LOREM_IPSUM.substr(0, (itemHash % 301) + 20),
|
||||
key: String(ii),
|
||||
pressed: false,
|
||||
});
|
||||
for (let i = start; i < count + start; i++) {
|
||||
dataBlob.push(genItemData(i));
|
||||
}
|
||||
return dataBlob;
|
||||
}
|
||||
|
||||
function genOlderItems(count: number, start: number = 0): Array<Item> {
|
||||
const dataBlob = [];
|
||||
for (let i = count; i > 0; i--) {
|
||||
dataBlob.push(genItemData(start - i));
|
||||
}
|
||||
return dataBlob;
|
||||
}
|
||||
@@ -147,6 +160,12 @@ class SeparatorComponent extends React.PureComponent<{...}> {
|
||||
}
|
||||
}
|
||||
|
||||
const LoadingComponent: React.ComponentType<{}> = React.memo(() => (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
));
|
||||
|
||||
class ItemSeparatorComponent extends React.PureComponent<$FlowFixMeProps> {
|
||||
render(): React.Node {
|
||||
const style = this.props.highlighted
|
||||
@@ -352,6 +371,13 @@ const styles = StyleSheet.create({
|
||||
text: {
|
||||
flex: 1,
|
||||
},
|
||||
loadingContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: 100,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: 'rgb(200, 199, 204)',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
@@ -362,8 +388,10 @@ module.exports = {
|
||||
ItemSeparatorComponent,
|
||||
PlainInput,
|
||||
SeparatorComponent,
|
||||
LoadingComponent,
|
||||
Spindicator,
|
||||
genItemData,
|
||||
genNewerItems,
|
||||
genOlderItems,
|
||||
getItemLayout,
|
||||
pressItem,
|
||||
renderSmallSwitchOption,
|
||||
|
||||
@@ -35,8 +35,10 @@ import {
|
||||
ItemSeparatorComponent,
|
||||
PlainInput,
|
||||
SeparatorComponent,
|
||||
LoadingComponent,
|
||||
Spindicator,
|
||||
genItemData,
|
||||
genNewerItems,
|
||||
genOlderItems,
|
||||
getItemLayout,
|
||||
pressItem,
|
||||
renderSmallSwitchOption,
|
||||
@@ -44,6 +46,11 @@ import {
|
||||
|
||||
import type {Item} from '../../components/ListExampleShared';
|
||||
|
||||
const PAGE_SIZE = 100;
|
||||
const NUM_PAGES = 10;
|
||||
const INITIAL_PAGE_OFFSET = Math.floor(NUM_PAGES / 2);
|
||||
const LOAD_TIME = 2000;
|
||||
|
||||
const VIEWABILITY_CONFIG = {
|
||||
minimumViewTime: 3000,
|
||||
viewAreaCoveragePercentThreshold: 100,
|
||||
@@ -53,6 +60,8 @@ const VIEWABILITY_CONFIG = {
|
||||
type Props = $ReadOnly<{||}>;
|
||||
type State = {|
|
||||
data: Array<Item>,
|
||||
first: number,
|
||||
last: number,
|
||||
debug: boolean,
|
||||
horizontal: boolean,
|
||||
inverted: boolean,
|
||||
@@ -66,13 +75,18 @@ type State = {|
|
||||
onPressDisabled: boolean,
|
||||
textSelectable: boolean,
|
||||
isRTL: boolean,
|
||||
maintainVisibleContentPosition: boolean,
|
||||
previousLoading: boolean,
|
||||
nextLoading: boolean,
|
||||
|};
|
||||
|
||||
const IS_RTL = I18nManager.isRTL;
|
||||
|
||||
class FlatListExample extends React.PureComponent<Props, State> {
|
||||
state: State = {
|
||||
data: genItemData(100),
|
||||
data: genNewerItems(PAGE_SIZE, PAGE_SIZE * INITIAL_PAGE_OFFSET),
|
||||
first: PAGE_SIZE * INITIAL_PAGE_OFFSET,
|
||||
last: PAGE_SIZE + PAGE_SIZE * INITIAL_PAGE_OFFSET,
|
||||
debug: false,
|
||||
horizontal: false,
|
||||
inverted: false,
|
||||
@@ -86,6 +100,9 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
onPressDisabled: false,
|
||||
textSelectable: true,
|
||||
isRTL: IS_RTL,
|
||||
maintainVisibleContentPosition: true,
|
||||
previousLoading: false,
|
||||
nextLoading: false,
|
||||
};
|
||||
|
||||
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
|
||||
@@ -209,6 +226,11 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
this.state.isRTL,
|
||||
this._setIsRTL,
|
||||
)}
|
||||
{renderSmallSwitchOption(
|
||||
'Maintain content position',
|
||||
this.state.maintainVisibleContentPosition,
|
||||
this._setBooleanValue('maintainVisibleContentPosition'),
|
||||
)}
|
||||
{Platform.OS === 'android' && (
|
||||
<View>
|
||||
<TextInput
|
||||
@@ -230,8 +252,12 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
<Animated.FlatList
|
||||
fadingEdgeLength={this.state.fadingEdgeLength}
|
||||
ItemSeparatorComponent={ItemSeparatorComponent}
|
||||
ListHeaderComponent={<HeaderComponent />}
|
||||
ListFooterComponent={FooterComponent}
|
||||
ListHeaderComponent={
|
||||
this.state.previousLoading ? LoadingComponent : HeaderComponent
|
||||
}
|
||||
ListFooterComponent={
|
||||
this.state.nextLoading ? LoadingComponent : FooterComponent
|
||||
}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
// $FlowFixMe[missing-empty-array-annot]
|
||||
data={this.state.empty ? [] : filteredData}
|
||||
@@ -250,6 +276,8 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
keyboardShouldPersistTaps="always"
|
||||
keyboardDismissMode="on-drag"
|
||||
numColumns={1}
|
||||
onStartReached={this._onStartReached}
|
||||
initialScrollIndex={Math.floor(PAGE_SIZE / 2)}
|
||||
onEndReached={this._onEndReached}
|
||||
onRefresh={this._onRefresh}
|
||||
onScroll={
|
||||
@@ -260,6 +288,11 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
refreshing={false}
|
||||
contentContainerStyle={styles.list}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
maintainVisibleContentPosition={
|
||||
this.state.maintainVisibleContentPosition
|
||||
? {minIndexForVisible: 0}
|
||||
: undefined
|
||||
}
|
||||
{...flatListItemRendererProps}
|
||||
/>
|
||||
</View>
|
||||
@@ -280,13 +313,33 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
_getItemLayout = (data: any, index: number) => {
|
||||
return getItemLayout(data, index, this.state.horizontal);
|
||||
};
|
||||
_onEndReached = () => {
|
||||
if (this.state.data.length >= 1000) {
|
||||
_onStartReached = () => {
|
||||
if (this.state.first <= 0 || this.state.previousLoading) {
|
||||
return;
|
||||
}
|
||||
this.setState(state => ({
|
||||
data: state.data.concat(genItemData(100, state.data.length)),
|
||||
}));
|
||||
|
||||
this.setState({previousLoading: true});
|
||||
setTimeout(() => {
|
||||
this.setState(state => ({
|
||||
previousLoading: false,
|
||||
data: genOlderItems(PAGE_SIZE, state.first).concat(state.data),
|
||||
first: state.first - PAGE_SIZE,
|
||||
}));
|
||||
}, LOAD_TIME);
|
||||
};
|
||||
_onEndReached = () => {
|
||||
if (this.state.last >= PAGE_SIZE * NUM_PAGES || this.state.nextLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({nextLoading: true});
|
||||
setTimeout(() => {
|
||||
this.setState(state => ({
|
||||
nextLoading: false,
|
||||
data: state.data.concat(genNewerItems(PAGE_SIZE, state.last)),
|
||||
last: state.last + PAGE_SIZE,
|
||||
}));
|
||||
}, LOAD_TIME);
|
||||
};
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
_onPressCallback = () => {
|
||||
@@ -343,7 +396,7 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
|
||||
_pressItem = (key: string) => {
|
||||
this._listRef?.recordInteraction();
|
||||
const index = Number(key);
|
||||
const index = this.state.data.findIndex(item => item.key === key);
|
||||
const itemState = pressItem(this.state.data[index]);
|
||||
this.setState(state => ({
|
||||
...state,
|
||||
|
||||
@@ -23,7 +23,7 @@ const {
|
||||
ItemComponent,
|
||||
PlainInput,
|
||||
SeparatorComponent,
|
||||
genItemData,
|
||||
genNewerItems,
|
||||
getItemLayout,
|
||||
pressItem,
|
||||
renderSmallSwitchOption,
|
||||
@@ -46,7 +46,7 @@ class MultiColumnExample extends React.PureComponent<
|
||||
numColumns: number,
|
||||
virtualized: boolean,
|
||||
|} = {
|
||||
data: genItemData(1000),
|
||||
data: genNewerItems(1000),
|
||||
filterText: '',
|
||||
fixedHeight: true,
|
||||
logViewable: false,
|
||||
|
||||
@@ -22,7 +22,7 @@ const {
|
||||
PlainInput,
|
||||
SeparatorComponent,
|
||||
Spindicator,
|
||||
genItemData,
|
||||
genNewerItems,
|
||||
pressItem,
|
||||
renderSmallSwitchOption,
|
||||
renderStackedItem,
|
||||
@@ -170,7 +170,7 @@ export function SectionList_scrollable(Props: {
|
||||
const [logViewable, setLogViewable] = React.useState(false);
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
const [inverted, setInverted] = React.useState(false);
|
||||
const [data, setData] = React.useState(genItemData(1000));
|
||||
const [data, setData] = React.useState(genNewerItems(1000));
|
||||
|
||||
const filterRegex = new RegExp(String(filterText), 'i');
|
||||
const filter = (item: Item) =>
|
||||
|
||||
@@ -73,6 +73,10 @@ type ViewabilityHelperCallbackTuple = {
|
||||
type State = {
|
||||
renderMask: CellRenderMask,
|
||||
cellsAroundViewport: {first: number, last: number},
|
||||
// Used to track items added at the start of the list for maintainVisibleContentPosition.
|
||||
firstVisibleItemKey: ?string,
|
||||
// When > 0 the scroll position available in JS is considered stale and should not be used.
|
||||
pendingScrollUpdateCount: number,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -448,9 +452,24 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
|
||||
const initialRenderRegion = VirtualizedList._initialRenderRegion(props);
|
||||
|
||||
const minIndexForVisible =
|
||||
this.props.maintainVisibleContentPosition?.minIndexForVisible ?? 0;
|
||||
|
||||
this.state = {
|
||||
cellsAroundViewport: initialRenderRegion,
|
||||
renderMask: VirtualizedList._createRenderMask(props, initialRenderRegion),
|
||||
firstVisibleItemKey:
|
||||
this.props.getItemCount(this.props.data) > minIndexForVisible
|
||||
? VirtualizedList._getItemKey(this.props, minIndexForVisible)
|
||||
: null,
|
||||
// When we have a non-zero initialScrollIndex, we will receive a
|
||||
// scroll event later so this will prevent the window from updating
|
||||
// until we get a valid offset.
|
||||
pendingScrollUpdateCount:
|
||||
this.props.initialScrollIndex != null &&
|
||||
this.props.initialScrollIndex > 0
|
||||
? 1
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -502,6 +521,40 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
static _findItemIndexWithKey(
|
||||
props: Props,
|
||||
key: string,
|
||||
hint: ?number,
|
||||
): ?number {
|
||||
const itemCount = props.getItemCount(props.data);
|
||||
if (hint != null && hint >= 0 && hint < itemCount) {
|
||||
const curKey = VirtualizedList._getItemKey(props, hint);
|
||||
if (curKey === key) {
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
for (let ii = 0; ii < itemCount; ii++) {
|
||||
const curKey = VirtualizedList._getItemKey(props, ii);
|
||||
if (curKey === key) {
|
||||
return ii;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static _getItemKey(
|
||||
props: {
|
||||
data: Props['data'],
|
||||
getItem: Props['getItem'],
|
||||
keyExtractor: Props['keyExtractor'],
|
||||
...
|
||||
},
|
||||
index: number,
|
||||
): string {
|
||||
const item = props.getItem(props.data, index);
|
||||
return VirtualizedList._keyExtractor(item, index, props);
|
||||
}
|
||||
|
||||
static _createRenderMask(
|
||||
props: Props,
|
||||
cellsAroundViewport: {first: number, last: number},
|
||||
@@ -585,6 +638,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
_adjustCellsAroundViewport(
|
||||
props: Props,
|
||||
cellsAroundViewport: {first: number, last: number},
|
||||
pendingScrollUpdateCount: number,
|
||||
): {first: number, last: number} {
|
||||
const {data, getItemCount} = props;
|
||||
const onEndReachedThreshold = onEndReachedThresholdOrDefault(
|
||||
@@ -616,21 +670,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
),
|
||||
};
|
||||
} else {
|
||||
// If we have a non-zero initialScrollIndex and run this before we've scrolled,
|
||||
// we'll wipe out the initialNumToRender rendered elements starting at initialScrollIndex.
|
||||
// So let's wait until we've scrolled the view to the right place. And until then,
|
||||
// we will trust the initialScrollIndex suggestion.
|
||||
|
||||
// Thus, we want to recalculate the windowed render limits if any of the following hold:
|
||||
// - initialScrollIndex is undefined or is 0
|
||||
// - initialScrollIndex > 0 AND scrolling is complete
|
||||
// - initialScrollIndex > 0 AND the end of the list is visible (this handles the case
|
||||
// where the list is shorter than the visible area)
|
||||
if (
|
||||
props.initialScrollIndex &&
|
||||
!this._scrollMetrics.offset &&
|
||||
Math.abs(distanceFromEnd) >= Number.EPSILON
|
||||
) {
|
||||
// If we have a pending scroll update, we should not adjust the render window as it
|
||||
// might override the correct window.
|
||||
if (pendingScrollUpdateCount > 0) {
|
||||
return cellsAroundViewport.last >= getItemCount(data)
|
||||
? VirtualizedList._constrainToItemCount(cellsAroundViewport, props)
|
||||
: cellsAroundViewport;
|
||||
@@ -712,14 +754,59 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
let maintainVisibleContentPositionAdjustment: ?number = null;
|
||||
const prevFirstVisibleItemKey = prevState.firstVisibleItemKey;
|
||||
const minIndexForVisible =
|
||||
newProps.maintainVisibleContentPosition?.minIndexForVisible ?? 0;
|
||||
const newFirstVisibleItemKey =
|
||||
newProps.getItemCount(newProps.data) > minIndexForVisible
|
||||
? VirtualizedList._getItemKey(newProps, minIndexForVisible)
|
||||
: null;
|
||||
if (
|
||||
newProps.maintainVisibleContentPosition != null &&
|
||||
prevFirstVisibleItemKey != null &&
|
||||
newFirstVisibleItemKey != null
|
||||
) {
|
||||
if (newFirstVisibleItemKey !== prevFirstVisibleItemKey) {
|
||||
// Fast path if items were added at the start of the list.
|
||||
const hint =
|
||||
itemCount - prevState.renderMask.numCells() + minIndexForVisible;
|
||||
const firstVisibleItemIndex = VirtualizedList._findItemIndexWithKey(
|
||||
newProps,
|
||||
prevFirstVisibleItemKey,
|
||||
hint,
|
||||
);
|
||||
maintainVisibleContentPositionAdjustment =
|
||||
firstVisibleItemIndex != null
|
||||
? firstVisibleItemIndex - minIndexForVisible
|
||||
: null;
|
||||
} else {
|
||||
maintainVisibleContentPositionAdjustment = null;
|
||||
}
|
||||
}
|
||||
|
||||
const constrainedCells = VirtualizedList._constrainToItemCount(
|
||||
prevState.cellsAroundViewport,
|
||||
maintainVisibleContentPositionAdjustment != null
|
||||
? {
|
||||
first:
|
||||
prevState.cellsAroundViewport.first +
|
||||
maintainVisibleContentPositionAdjustment,
|
||||
last:
|
||||
prevState.cellsAroundViewport.last +
|
||||
maintainVisibleContentPositionAdjustment,
|
||||
}
|
||||
: prevState.cellsAroundViewport,
|
||||
newProps,
|
||||
);
|
||||
|
||||
return {
|
||||
cellsAroundViewport: constrainedCells,
|
||||
renderMask: VirtualizedList._createRenderMask(newProps, constrainedCells),
|
||||
firstVisibleItemKey: newFirstVisibleItemKey,
|
||||
pendingScrollUpdateCount:
|
||||
maintainVisibleContentPositionAdjustment != null
|
||||
? prevState.pendingScrollUpdateCount + 1
|
||||
: prevState.pendingScrollUpdateCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -751,7 +838,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
|
||||
for (let ii = first; ii <= last; ii++) {
|
||||
const item = getItem(data, ii);
|
||||
const key = this._keyExtractor(item, ii, this.props);
|
||||
const key = VirtualizedList._keyExtractor(item, ii, this.props);
|
||||
|
||||
this._indicesToKeys.set(ii, key);
|
||||
if (stickyIndicesFromProps.has(ii + stickyOffset)) {
|
||||
@@ -824,15 +911,14 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
_getSpacerKey = (isVertical: boolean): string =>
|
||||
isVertical ? 'height' : 'width';
|
||||
|
||||
_keyExtractor(
|
||||
static _keyExtractor(
|
||||
item: Item,
|
||||
index: number,
|
||||
props: {
|
||||
keyExtractor?: ?(item: Item, index: number) => string,
|
||||
...
|
||||
},
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
) {
|
||||
): string {
|
||||
if (props.keyExtractor != null) {
|
||||
return props.keyExtractor(item, index);
|
||||
}
|
||||
@@ -878,6 +964,10 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
cellKey={this._getCellKey() + '-header'}
|
||||
key="$header">
|
||||
<View
|
||||
// We expect that header component will be a single native view so make it
|
||||
// not collapsable to avoid this view being flattened and make this assumption
|
||||
// no longer true.
|
||||
collapsable={false}
|
||||
onLayout={this._onLayoutHeader}
|
||||
style={StyleSheet.compose(
|
||||
inversionStyle,
|
||||
@@ -1035,6 +1125,16 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
style: inversionStyle
|
||||
? [inversionStyle, this.props.style]
|
||||
: this.props.style,
|
||||
maintainVisibleContentPosition:
|
||||
this.props.maintainVisibleContentPosition != null
|
||||
? {
|
||||
...this.props.maintainVisibleContentPosition,
|
||||
// Adjust index to account for ListHeaderComponent.
|
||||
minIndexForVisible:
|
||||
this.props.maintainVisibleContentPosition.minIndexForVisible +
|
||||
(this.props.ListHeaderComponent ? 1 : 0),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
this._hasMore = this.state.cellsAroundViewport.last < itemCount - 1;
|
||||
@@ -1456,8 +1556,13 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
onStartReachedThreshold,
|
||||
onEndReached,
|
||||
onEndReachedThreshold,
|
||||
initialScrollIndex,
|
||||
} = this.props;
|
||||
// If we have any pending scroll updates it means that the scroll metrics
|
||||
// are out of date and we should not call any of the edge reached callbacks.
|
||||
if (this.state.pendingScrollUpdateCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {contentLength, visibleLength, offset} = this._scrollMetrics;
|
||||
let distanceFromStart = offset;
|
||||
let distanceFromEnd = contentLength - visibleLength - offset;
|
||||
@@ -1509,14 +1614,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
isWithinStartThreshold &&
|
||||
this._scrollMetrics.contentLength !== this._sentStartForContentLength
|
||||
) {
|
||||
// On initial mount when using initialScrollIndex the offset will be 0 initially
|
||||
// and will trigger an unexpected onStartReached. To avoid this we can use
|
||||
// timestamp to differentiate between the initial scroll metrics and when we actually
|
||||
// received the first scroll event.
|
||||
if (!initialScrollIndex || this._scrollMetrics.timestamp !== 0) {
|
||||
this._sentStartForContentLength = this._scrollMetrics.contentLength;
|
||||
onStartReached({distanceFromStart});
|
||||
}
|
||||
this._sentStartForContentLength = this._scrollMetrics.contentLength;
|
||||
onStartReached({distanceFromStart});
|
||||
}
|
||||
|
||||
// If the user scrolls away from the start or end and back again,
|
||||
@@ -1643,6 +1742,11 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
visibleLength,
|
||||
zoomScale,
|
||||
};
|
||||
if (this.state.pendingScrollUpdateCount > 0) {
|
||||
this.setState(state => ({
|
||||
pendingScrollUpdateCount: state.pendingScrollUpdateCount - 1,
|
||||
}));
|
||||
}
|
||||
this._updateViewableItems(this.props, this.state.cellsAroundViewport);
|
||||
if (!this.props) {
|
||||
return;
|
||||
@@ -1758,6 +1862,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
const cellsAroundViewport = this._adjustCellsAroundViewport(
|
||||
props,
|
||||
state.cellsAroundViewport,
|
||||
state.pendingScrollUpdateCount,
|
||||
);
|
||||
const renderMask = VirtualizedList._createRenderMask(
|
||||
props,
|
||||
@@ -1788,7 +1893,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
return {
|
||||
index,
|
||||
item,
|
||||
key: this._keyExtractor(item, index, props),
|
||||
key: VirtualizedList._keyExtractor(item, index, props),
|
||||
isViewable,
|
||||
};
|
||||
};
|
||||
@@ -1849,13 +1954,12 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
inLayout?: boolean,
|
||||
...
|
||||
} => {
|
||||
const {data, getItem, getItemCount, getItemLayout} = props;
|
||||
const {data, getItemCount, getItemLayout} = props;
|
||||
invariant(
|
||||
index >= 0 && index < getItemCount(data),
|
||||
'Tried to get frame for out of range index ' + index,
|
||||
);
|
||||
const item = getItem(data, index);
|
||||
const frame = this._frames[this._keyExtractor(item, index, props)];
|
||||
const frame = this._frames[VirtualizedList._getItemKey(props, index)];
|
||||
if (!frame || frame.index !== index) {
|
||||
if (getItemLayout) {
|
||||
/* $FlowFixMe[prop-missing] (>=0.63.0 site=react_native_fb) This comment
|
||||
@@ -1890,11 +1994,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
// where it is.
|
||||
if (
|
||||
focusedCellIndex >= itemCount ||
|
||||
this._keyExtractor(
|
||||
props.getItem(props.data, focusedCellIndex),
|
||||
focusedCellIndex,
|
||||
props,
|
||||
) !== this._lastFocusedCellKey
|
||||
VirtualizedList._getItemKey(props, focusedCellIndex) !==
|
||||
this._lastFocusedCellKey
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
@@ -1935,6 +2036,11 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
||||
props: FrameMetricProps,
|
||||
cellsAroundViewport: {first: number, last: number},
|
||||
) {
|
||||
// If we have any pending scroll updates it means that the scroll metrics
|
||||
// are out of date and we should not call any of the visibility callbacks.
|
||||
if (this.state.pendingScrollUpdateCount > 0) {
|
||||
return;
|
||||
}
|
||||
this._viewabilityTuples.forEach(tuple => {
|
||||
tuple.viewabilityHelper.onUpdate(
|
||||
props,
|
||||
|
||||
@@ -2164,10 +2164,70 @@ it('virtualizes away last focused index if item removed', () => {
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
function generateItems(count) {
|
||||
it('handles maintainVisibleContentPosition', () => {
|
||||
const items = generateItems(20);
|
||||
const ITEM_HEIGHT = 10;
|
||||
|
||||
let component;
|
||||
ReactTestRenderer.act(() => {
|
||||
component = ReactTestRenderer.create(
|
||||
<VirtualizedList
|
||||
initialNumToRender={1}
|
||||
windowSize={1}
|
||||
maintainVisibleContentPosition={{minIndexForVisible: 0}}
|
||||
{...baseItemProps(items)}
|
||||
{...fixedHeightItemLayoutProps(ITEM_HEIGHT)}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
ReactTestRenderer.act(() => {
|
||||
simulateLayout(component, {
|
||||
viewport: {width: 10, height: 50},
|
||||
content: {width: 10, height: items.length * ITEM_HEIGHT},
|
||||
});
|
||||
|
||||
performAllBatches();
|
||||
});
|
||||
|
||||
// Initial render.
|
||||
expect(component).toMatchSnapshot();
|
||||
|
||||
// Add new items at the start of the list to trigger the maintainVisibleContentPosition adjustment.
|
||||
const newItems = [...generateItems(10, items.length), ...items];
|
||||
ReactTestRenderer.act(() => {
|
||||
component.update(
|
||||
<VirtualizedList
|
||||
initialNumToRender={1}
|
||||
windowSize={1}
|
||||
maintainVisibleContentPosition={{minIndexForVisible: 0}}
|
||||
{...baseItemProps(newItems)}
|
||||
{...fixedHeightItemLayoutProps(ITEM_HEIGHT)}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
// Previously rendered cells should be rendered still.
|
||||
expect(component).toMatchSnapshot();
|
||||
|
||||
// Simulate scroll adjustment from native maintainVisibleContentPosition.
|
||||
ReactTestRenderer.act(() => {
|
||||
simulateContentLayout(component, {
|
||||
width: 10,
|
||||
height: newItems.length * ITEM_HEIGHT,
|
||||
});
|
||||
simulateScroll(component, {x: 0, y: 10 * ITEM_HEIGHT});
|
||||
performAllBatches();
|
||||
});
|
||||
|
||||
// Previously rendered cells should be rendered still + starting to render new cells ahead.
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
function generateItems(count, startKey = 0) {
|
||||
return Array(count)
|
||||
.fill()
|
||||
.map((_, i) => ({key: i}));
|
||||
.map((_, i) => ({key: i + startKey}));
|
||||
}
|
||||
|
||||
function generateItemsStickyEveryN(count, n) {
|
||||
@@ -2225,7 +2285,7 @@ function simulateContentLayout(component, dimensions) {
|
||||
|
||||
function simulateCellLayout(component, items, itemIndex, dimensions) {
|
||||
const instance = component.getInstance();
|
||||
const cellKey = instance._keyExtractor(
|
||||
const cellKey = VirtualizedList._keyExtractor(
|
||||
items[itemIndex],
|
||||
itemIndex,
|
||||
instance.props,
|
||||
|
||||
+572
-55
@@ -65,6 +65,7 @@ exports[`VirtualizedList forwards correct stickyHeaderIndices when ListHeaderCom
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
collapsable={false}
|
||||
onLayout={[Function]}
|
||||
>
|
||||
<Header />
|
||||
@@ -1048,6 +1049,7 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
|
||||
<RCTRefreshControl />
|
||||
<View>
|
||||
<View
|
||||
collapsable={false}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -1249,6 +1251,7 @@ exports[`VirtualizedList renders empty list with empty component 1`] = `
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
collapsable={false}
|
||||
onLayout={[Function]}
|
||||
>
|
||||
<header />
|
||||
@@ -1834,45 +1837,12 @@ exports[`adjusts render area with non-zero initialScrollIndex 1`] = `
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={15}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={16}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={17}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={18}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={19}
|
||||
/>
|
||||
</View>
|
||||
style={
|
||||
Object {
|
||||
"height": 50,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
@@ -3121,10 +3091,566 @@ exports[`gracefully handles negative initialScrollIndex 1`] = `
|
||||
value={3}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={4}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={5}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={6}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={7}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={8}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={9}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`handles maintainVisibleContentPosition 1`] = `
|
||||
<RCTScrollView
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": 0,
|
||||
},
|
||||
Object {
|
||||
"key": 1,
|
||||
},
|
||||
Object {
|
||||
"key": 2,
|
||||
},
|
||||
Object {
|
||||
"key": 3,
|
||||
},
|
||||
Object {
|
||||
"key": 4,
|
||||
},
|
||||
Object {
|
||||
"key": 5,
|
||||
},
|
||||
Object {
|
||||
"key": 6,
|
||||
},
|
||||
Object {
|
||||
"key": 7,
|
||||
},
|
||||
Object {
|
||||
"key": 8,
|
||||
},
|
||||
Object {
|
||||
"key": 9,
|
||||
},
|
||||
Object {
|
||||
"key": 10,
|
||||
},
|
||||
Object {
|
||||
"key": 11,
|
||||
},
|
||||
Object {
|
||||
"key": 12,
|
||||
},
|
||||
Object {
|
||||
"key": 13,
|
||||
},
|
||||
Object {
|
||||
"key": 14,
|
||||
},
|
||||
Object {
|
||||
"key": 15,
|
||||
},
|
||||
Object {
|
||||
"key": 16,
|
||||
},
|
||||
Object {
|
||||
"key": 17,
|
||||
},
|
||||
Object {
|
||||
"key": 18,
|
||||
},
|
||||
Object {
|
||||
"key": 19,
|
||||
},
|
||||
]
|
||||
}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
getItemLayout={[Function]}
|
||||
initialNumToRender={1}
|
||||
maintainVisibleContentPosition={
|
||||
Object {
|
||||
"minIndexForVisible": 0,
|
||||
}
|
||||
}
|
||||
onContentSizeChange={[Function]}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollBegin={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
windowSize={1}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={0}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={1}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={2}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={3}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={4}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"height": 60,
|
||||
"height": 150,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`handles maintainVisibleContentPosition 2`] = `
|
||||
<RCTScrollView
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": 20,
|
||||
},
|
||||
Object {
|
||||
"key": 21,
|
||||
},
|
||||
Object {
|
||||
"key": 22,
|
||||
},
|
||||
Object {
|
||||
"key": 23,
|
||||
},
|
||||
Object {
|
||||
"key": 24,
|
||||
},
|
||||
Object {
|
||||
"key": 25,
|
||||
},
|
||||
Object {
|
||||
"key": 26,
|
||||
},
|
||||
Object {
|
||||
"key": 27,
|
||||
},
|
||||
Object {
|
||||
"key": 28,
|
||||
},
|
||||
Object {
|
||||
"key": 29,
|
||||
},
|
||||
Object {
|
||||
"key": 0,
|
||||
},
|
||||
Object {
|
||||
"key": 1,
|
||||
},
|
||||
Object {
|
||||
"key": 2,
|
||||
},
|
||||
Object {
|
||||
"key": 3,
|
||||
},
|
||||
Object {
|
||||
"key": 4,
|
||||
},
|
||||
Object {
|
||||
"key": 5,
|
||||
},
|
||||
Object {
|
||||
"key": 6,
|
||||
},
|
||||
Object {
|
||||
"key": 7,
|
||||
},
|
||||
Object {
|
||||
"key": 8,
|
||||
},
|
||||
Object {
|
||||
"key": 9,
|
||||
},
|
||||
Object {
|
||||
"key": 10,
|
||||
},
|
||||
Object {
|
||||
"key": 11,
|
||||
},
|
||||
Object {
|
||||
"key": 12,
|
||||
},
|
||||
Object {
|
||||
"key": 13,
|
||||
},
|
||||
Object {
|
||||
"key": 14,
|
||||
},
|
||||
Object {
|
||||
"key": 15,
|
||||
},
|
||||
Object {
|
||||
"key": 16,
|
||||
},
|
||||
Object {
|
||||
"key": 17,
|
||||
},
|
||||
Object {
|
||||
"key": 18,
|
||||
},
|
||||
Object {
|
||||
"key": 19,
|
||||
},
|
||||
]
|
||||
}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
getItemLayout={[Function]}
|
||||
initialNumToRender={1}
|
||||
maintainVisibleContentPosition={
|
||||
Object {
|
||||
"minIndexForVisible": 0,
|
||||
}
|
||||
}
|
||||
onContentSizeChange={[Function]}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollBegin={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
windowSize={1}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={20}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"height": 90,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={0}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={1}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={2}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={3}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={4}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"height": 150,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`handles maintainVisibleContentPosition 3`] = `
|
||||
<RCTScrollView
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": 20,
|
||||
},
|
||||
Object {
|
||||
"key": 21,
|
||||
},
|
||||
Object {
|
||||
"key": 22,
|
||||
},
|
||||
Object {
|
||||
"key": 23,
|
||||
},
|
||||
Object {
|
||||
"key": 24,
|
||||
},
|
||||
Object {
|
||||
"key": 25,
|
||||
},
|
||||
Object {
|
||||
"key": 26,
|
||||
},
|
||||
Object {
|
||||
"key": 27,
|
||||
},
|
||||
Object {
|
||||
"key": 28,
|
||||
},
|
||||
Object {
|
||||
"key": 29,
|
||||
},
|
||||
Object {
|
||||
"key": 0,
|
||||
},
|
||||
Object {
|
||||
"key": 1,
|
||||
},
|
||||
Object {
|
||||
"key": 2,
|
||||
},
|
||||
Object {
|
||||
"key": 3,
|
||||
},
|
||||
Object {
|
||||
"key": 4,
|
||||
},
|
||||
Object {
|
||||
"key": 5,
|
||||
},
|
||||
Object {
|
||||
"key": 6,
|
||||
},
|
||||
Object {
|
||||
"key": 7,
|
||||
},
|
||||
Object {
|
||||
"key": 8,
|
||||
},
|
||||
Object {
|
||||
"key": 9,
|
||||
},
|
||||
Object {
|
||||
"key": 10,
|
||||
},
|
||||
Object {
|
||||
"key": 11,
|
||||
},
|
||||
Object {
|
||||
"key": 12,
|
||||
},
|
||||
Object {
|
||||
"key": 13,
|
||||
},
|
||||
Object {
|
||||
"key": 14,
|
||||
},
|
||||
Object {
|
||||
"key": 15,
|
||||
},
|
||||
Object {
|
||||
"key": 16,
|
||||
},
|
||||
Object {
|
||||
"key": 17,
|
||||
},
|
||||
Object {
|
||||
"key": 18,
|
||||
},
|
||||
Object {
|
||||
"key": 19,
|
||||
},
|
||||
]
|
||||
}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
getItemLayout={[Function]}
|
||||
initialNumToRender={1}
|
||||
maintainVisibleContentPosition={
|
||||
Object {
|
||||
"minIndexForVisible": 0,
|
||||
}
|
||||
}
|
||||
onContentSizeChange={[Function]}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollBegin={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
windowSize={1}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={20}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={29}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={0}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={1}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={2}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={3}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={4}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"height": 150,
|
||||
}
|
||||
}
|
||||
/>
|
||||
@@ -4072,21 +4598,12 @@ exports[`renders new items when data is updated with non-zero initialScrollIndex
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={2}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<MockCellItem
|
||||
value={3}
|
||||
/>
|
||||
</View>
|
||||
style={
|
||||
Object {
|
||||
"height": 20,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
+2
@@ -831,6 +831,7 @@ exports[`VirtualizedSectionList renders all the bells and whistles 1`] = `
|
||||
<RCTRefreshControl />
|
||||
<View>
|
||||
<View
|
||||
collapsable={false}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -1056,6 +1057,7 @@ exports[`VirtualizedSectionList renders empty list with empty component 1`] = `
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
collapsable={false}
|
||||
onLayout={[Function]}
|
||||
>
|
||||
<header />
|
||||
|
||||
Reference in New Issue
Block a user