VirtualizedList-63-64

Created Diff never expires
143 removals
740 lines
141 additions
738 lines
/**
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* Copyright (c) Facebook, Inc. and its affiliates.
*
*
* This source code is licensed under the MIT license found in the
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* LICENSE file in the root directory of this source tree.
*
*
* @flow
* @flow
* @format
* @format
*/
*/


'use strict';
'use strict';


const Batchinator = require('../Interaction/Batchinator');
const Batchinator = require('../Interaction/Batchinator');
const FillRateHelper = require('./FillRateHelper');
const FillRateHelper = require('./FillRateHelper');
const PropTypes = require('prop-types');
const React = require('react');
const ReactNative = require('../Renderer/shims/ReactNative');
const ReactNative = require('../Renderer/shims/ReactNative');
const RefreshControl = require('../Components/RefreshControl/RefreshControl');
const RefreshControl = require('../Components/RefreshControl/RefreshControl');
const ScrollView = require('../Components/ScrollView/ScrollView');
const ScrollView = require('../Components/ScrollView/ScrollView');
const StyleSheet = require('../StyleSheet/StyleSheet');
const StyleSheet = require('../StyleSheet/StyleSheet');
const View = require('../Components/View/View');
const View = require('../Components/View/View');
const ViewabilityHelper = require('./ViewabilityHelper');
const ViewabilityHelper = require('./ViewabilityHelper');


const flattenStyle = require('../StyleSheet/flattenStyle');
const flattenStyle = require('../StyleSheet/flattenStyle');
const infoLog = require('../Utilities/infoLog');
const infoLog = require('../Utilities/infoLog');
const invariant = require('invariant');
const invariant = require('invariant');
const warning = require('fbjs/lib/warning');


const {computeWindowedRenderLimits} = require('./VirtualizeUtils');
const {computeWindowedRenderLimits} = require('./VirtualizeUtils');


import * as React from 'react';
import type {ScrollResponderType} from '../Components/ScrollView/ScrollView';
import type {ScrollResponderType} from '../Components/ScrollView/ScrollView';
import type {ViewStyleProp} from '../StyleSheet/StyleSheet';
import type {ViewStyleProp} from '../StyleSheet/StyleSheet';
import type {
import type {
ViewabilityConfig,
ViewabilityConfig,
ViewToken,
ViewToken,
ViewabilityConfigCallbackPair,
ViewabilityConfigCallbackPair,
} from './ViewabilityHelper';
} from './ViewabilityHelper';
import {
VirtualizedListCellContextProvider,
VirtualizedListContext,
VirtualizedListContextProvider,
type ChildListState,
type ListDebugInfo,
} from './VirtualizedListContext.js';


type Item = any;
type Item = any;


export type Separators = {
export type Separators = {
highlight: () => void,
highlight: () => void,
unhighlight: () => void,
unhighlight: () => void,
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
...
...
};
};


export type RenderItemProps<ItemT> = {
export type RenderItemProps<ItemT> = {
item: ItemT,
item: ItemT,
index: number,
index: number,
separators: Separators,
separators: Separators,
...
...
};
};


export type RenderItemType<ItemT> = (
export type RenderItemType<ItemT> = (
info: RenderItemProps<ItemT>,
info: RenderItemProps<ItemT>,
) => React.Node;
) => React.Node;


type ViewabilityHelperCallbackTuple = {
type ViewabilityHelperCallbackTuple = {
viewabilityHelper: ViewabilityHelper,
viewabilityHelper: ViewabilityHelper,
onViewableItemsChanged: (info: {
onViewableItemsChanged: (info: {
viewableItems: Array<ViewToken>,
viewableItems: Array<ViewToken>,
changed: Array<ViewToken>,
changed: Array<ViewToken>,
...
...
}) => void,
}) => void,
...
...
};
};


type RequiredProps = {|
type RequiredProps = {|
/**
/**
* The default accessor functions assume this is an Array<{key: string} | {id: string}> but you can override
* The default accessor functions assume this is an Array<{key: string} | {id: string}> but you can override
* getItem, getItemCount, and keyExtractor to handle any type of index-based data.
* getItem, getItemCount, and keyExtractor to handle any type of index-based data.
*/
*/
data?: any,
data?: any,
/**
/**
* A generic accessor for extracting an item from any sort of data blob.
* A generic accessor for extracting an item from any sort of data blob.
*/
*/
getItem: (data: any, index: number) => ?Item,
getItem: (data: any, index: number) => ?Item,
/**
/**
* Determines how many items are in the data blob.
* Determines how many items are in the data blob.
*/
*/
getItemCount: (data: any) => number,
getItemCount: (data: any) => number,
|};
|};
type OptionalProps = {|
type OptionalProps = {|
renderItem?: ?RenderItemType<Item>,
renderItem?: ?RenderItemType<Item>,
/**
/**
* `debug` will turn on extra logging and visual overlays to aid with debugging both usage and
* `debug` will turn on extra logging and visual overlays to aid with debugging both usage and
* implementation, but with a significant perf hit.
* implementation, but with a significant perf hit.
*/
*/
debug?: ?boolean,
debug?: ?boolean,
/**
/**
* DEPRECATED: Virtualization provides significant performance and memory optimizations, but fully
* DEPRECATED: Virtualization provides significant performance and memory optimizations, but fully
* unmounts react instances that are outside of the render window. You should only need to disable
* unmounts react instances that are outside of the render window. You should only need to disable
* this for debugging purposes.
* this for debugging purposes.
*/
*/
disableVirtualization?: ?boolean,
disableVirtualization?: ?boolean,
/**
/**
* A marker property for telling the list to re-render (since it implements `PureComponent`). If
* A marker property for telling the list to re-render (since it implements `PureComponent`). If
* any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the
* any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the
* `data` prop, stick it here and treat it immutably.
* `data` prop, stick it here and treat it immutably.
*/
*/
extraData?: any,
extraData?: any,
// e.g. height, y
// e.g. height, y
getItemLayout?: (
getItemLayout?: (
data: any,
data: any,
index: number,
index: number,
) => {
) => {
length: number,
length: number,
offset: number,
offset: number,
index: number,
index: number,
...
...
},
},
horizontal?: ?boolean,
horizontal?: ?boolean,
/**
/**
* How many items to render in the initial batch. This should be enough to fill the screen but not
* How many items to render in the initial batch. This should be enough to fill the screen but not
* much more. Note these items will never be unmounted as part of the windowed rendering in order
* much more. Note these items will never be unmounted as part of the windowed rendering in order
* to improve perceived performance of scroll-to-top actions.
* to improve perceived performance of scroll-to-top actions.
*/
*/
initialNumToRender: number,
initialNumToRender: number,
/**
/**
* Instead of starting at the top with the first item, start at `initialScrollIndex`. This
* Instead of starting at the top with the first item, start at `initialScrollIndex`. This
* disables the "scroll to top" optimization that keeps the first `initialNumToRender` items
* disables the "scroll to top" optimization that keeps the first `initialNumToRender` items
* always rendered and immediately renders the items starting at this initial index. Requires
* always rendered and immediately renders the items starting at this initial index. Requires
* `getItemLayout` to be implemented.
* `getItemLayout` to be implemented.
*/
*/
initialScrollIndex?: ?number,
initialScrollIndex?: ?number,
/**
/**
* Reverses the direction of scroll. Uses scale transforms of -1.
* Reverses the direction of scroll. Uses scale transforms of -1.
*/
*/
inverted?: ?boolean,
inverted?: ?boolean,
keyExtractor: (item: Item, index: number) => string,
keyExtractor: (item: Item, index: number) => string,
/**
/**
* Each cell is rendered using this element. Can be a React Component Class,
* Each cell is rendered using this element. Can be a React Component Class,
* or a render function. Defaults to using View.
* or a render function. Defaults to using View.
*/
*/
CellRendererComponent?: ?React.ComponentType<any>,
CellRendererComponent?: ?React.ComponentType<any>,
/**
/**
* Rendered in between each item, but not at the top or bottom. By default, `highlighted` and
* Rendered in between each item, but not at the top or bottom. By default, `highlighted` and
* `leadingItem` props are provided. `renderItem` provides `separators.highlight`/`unhighlight`
* `leadingItem` props are provided. `renderItem` provides `separators.highlight`/`unhighlight`
* which will update the `highlighted` prop, but you can also add custom props with
* which will update the `highlighted` prop, but you can also add custom props with
* `separators.updateProps`.
* `separators.updateProps`.
*/
*/
ItemSeparatorComponent?: ?React.ComponentType<any>,
ItemSeparatorComponent?: ?React.ComponentType<any>,
/**
/**
* Takes an item from `data` and renders it into the list. Example usage:
* Takes an item from `data` and renders it into the list. Example usage:
*
*
* <FlatList
* <FlatList
* ItemSeparatorComponent={Platform.OS !== 'android' && ({highlighted}) => (
* ItemSeparatorComponent={Platform.OS !== 'android' && ({highlighted}) => (
* <View style={[style.separator, highlighted && {marginLeft: 0}]} />
* <View style={[style.separator, highlighted && {marginLeft: 0}]} />
* )}
* )}
* data={[{title: 'Title Text', key: 'item1'}]}
* data={[{title: 'Title Text', key: 'item1'}]}
* ListItemComponent={({item, separators}) => (
* ListItemComponent={({item, separators}) => (
* <TouchableHighlight
* <TouchableHighlight
* onPress={() => this._onPress(item)}
* onPress={() => this._onPress(item)}
* onShowUnderlay={separators.highlight}
* onShowUnderlay={separators.highlight}
* onHideUnderlay={separators.unhighlight}>
* onHideUnderlay={separators.unhighlight}>
* <View style={{backgroundColor: 'white'}}>
* <View style={{backgroundColor: 'white'}}>
* <Text>{item.title}</Text>
* <Text>{item.title}</Text>
* </View>
* </View>
* </TouchableHighlight>
* </TouchableHighlight>
* )}
* )}
* />
* />
*
*
* Provides additional metadata like `index` if you need it, as well as a more generic
* Provides additional metadata like `index` if you need it, as well as a more generic
* `separators.updateProps` function which let's you set whatever props you want to change the
* `separators.updateProps` function which let's you set whatever props you want to change the
* rendering of either the leading separator or trailing separator in case the more common
* rendering of either the leading separator or trailing separator in case the more common
* `highlight` and `unhighlight` (which set the `highlighted: boolean` prop) are insufficient for
* `highlight` and `unhighlight` (which set the `highlighted: boolean` prop) are insufficient for
* your use-case.
* your use-case.
*/
*/
ListItemComponent?: ?(React.ComponentType<any> | React.Element<any>),
ListItemComponent?: ?(React.ComponentType<any> | React.Element<any>),
/**
/**
* Rendered when the list is empty. Can be a React Component Class, a render function, or
* Rendered when the list is empty. Can be a React Component Class, a render function, or
* a rendered element.
* a rendered element.
*/
*/
ListEmptyComponent?: ?(React.ComponentType<any> | React.Element<any>),
ListEmptyComponent?: ?(React.ComponentType<any> | React.Element<any>),
/**
/**
* Rendered at the bottom of all the items. Can be a React Component Class, a render function, or
* Rendered at the bottom of all the items. Can be a React Component Class, a render function, or
* a rendered element.
* a rendered element.
*/
*/
ListFooterComponent?: ?(React.ComponentType<any> | React.Element<any>),
ListFooterComponent?: ?(React.ComponentType<any> | React.Element<any>),
/**
/**
* Styling for internal View for ListFooterComponent
* Styling for internal View for ListFooterComponent
*/
*/
ListFooterComponentStyle?: ViewStyleProp,
ListFooterComponentStyle?: ViewStyleProp,
/**
/**
* Rendered at the top of all the items. Can be a React Component Class, a render function, or
* Rendered at the top of all the items. Can be a React Component Class, a render function, or
* a rendered element.
* a rendered element.
*/
*/
ListHeaderComponent?: ?(React.ComponentType<any> | React.Element<any>),
ListHeaderComponent?: ?(React.ComponentType<any> | React.Element<any>),
/**
/**
* Styling for internal View for ListHeaderComponent
* Styling for internal View for ListHeaderComponent
*/
*/
ListHeaderComponentStyle?: ViewStyleProp,
ListHeaderComponentStyle?: ViewStyleProp,
/**
/**
* A unique identifier for this list. If there are multiple VirtualizedLists at the same level of
* 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
* nesting within another VirtualizedList, this key is necessary for virtualization to
* work properly.
* work properly.
*/
*/
listKey?: string,
listKey?: string,
/**
/**
* The maximum number of items to render in each incremental render batch. The more rendered at
* The maximum number of items to render in each incremental render batch. The more rendered at
* once, the better the fill rate, but responsiveness may suffer because rendering content may
* once, the better the fill rate, but responsiveness may suffer because rendering content may
* interfere with responding to button taps or other interactions.
* interfere with responding to button taps or other interactions.
*/
*/
maxToRenderPerBatch: number,
maxToRenderPerBatch: number,
/**
/**
* Called once when the scroll position gets within `onEndReachedThreshold` of the rendered
* Called once when the scroll position gets within `onEndReachedThreshold` of the rendered
* content.
* content.
*/
*/
onEndReached?: ?(info: {distanceFromEnd: number, ...}) => void,
onEndReached?: ?(info: {distanceFromEnd: number, ...}) => void,
/**
/**
* How far from the end (in units of visible length of the list) the bottom edge of the
* How far from the end (in units of visible length of the list) the bottom edge of the
* list must be from the end of the content to trigger the `onEndReached` callback.
* list must be from the end of the content to trigger the `onEndReached` callback.
* Thus a value of 0.5 will trigger `onEndReached` when the end of the content is
* Thus a value of 0.5 will trigger `onEndReached` when the end of the content is
* within half the visible length of the list.
* within half the visible length of the list.
*/
*/
onEndReachedThreshold?: ?number,
onEndReachedThreshold?: ?number,
/**
/**
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
* sure to also set the `refreshing` prop correctly.
* sure to also set the `refreshing` prop correctly.
*/
*/
onRefresh?: ?() => void,
onRefresh?: ?() => void,
/**
/**
* Used to handle failures when scrolling to an index that has not been measured yet. Recommended
* Used to handle failures when scrolling to an index that has not been measured yet. Recommended
* action is to either compute your own offset and `scrollTo` it, or scroll as far as possible and
* action is to either compute your own offset and `scrollTo` it, or scroll as far as possible and
* then try again after more items have been rendered.
* then try again after more items have been rendered.
*/
*/
onScrollToIndexFailed?: ?(info: {
onScrollToIndexFailed?: ?(info: {
index: number,
index: number,
highestMeasuredFrameIndex: number,
highestMeasuredFrameIndex: number,
averageItemLength: number,
averageItemLength: number,
...
...
}) => void,
}) => void,
/**
/**
* Called when the viewability of rows changes, as defined by the
* Called when the viewability of rows changes, as defined by the
* `viewabilityConfig` prop.
* `viewabilityConfig` prop.
*/
*/
onViewableItemsChanged?: ?(info: {
onViewableItemsChanged?: ?(info: {
viewableItems: Array<ViewToken>,
viewableItems: Array<ViewToken>,
changed: Array<ViewToken>,
changed: Array<ViewToken>,
...
...
}) => void,
}) => void,
persistentScrollbar?: ?boolean,
persistentScrollbar?: ?boolean,
/**
/**
* Set this when offset is needed for the loading indicator to show correctly.
* Set this when offset is needed for the loading indicator to show correctly.
* @platform android
* @platform android
*/
*/
progressViewOffset?: number,
progressViewOffset?: number,
/**
/**
* A custom refresh control element. When set, it overrides the default
* A custom refresh control element. When set, it overrides the default
* <RefreshControl> component built internally. The onRefresh and refreshing
* <RefreshControl> component built internally. The onRefresh and refreshing
* props are also ignored. Only works for vertical VirtualizedList.
* props are also ignored. Only works for vertical VirtualizedList.
*/
*/
refreshControl?: ?React.Element<any>,
refreshControl?: ?React.Element<any>,
/**
/**
* Set this true while waiting for new data from a refresh.
* Set this true while waiting for new data from a refresh.
*/
*/
refreshing?: ?boolean,
refreshing?: ?boolean,
/**
/**
* Note: may have bugs (missing content) in some circumstances - use at your own risk.
* Note: may have bugs (missing content) in some circumstances - use at your own risk.
*
*
* This may improve scroll performance for large lists.
* This may improve scroll performance for large lists.
*/
*/
removeClippedSubviews?: boolean,
removeClippedSubviews?: boolean,
/**
/**
* Render a custom scroll component, e.g. with a differently styled `RefreshControl`.
* Render a custom scroll component, e.g. with a differently styled `RefreshControl`.
*/
*/
renderScrollComponent?: (props: Object) => React.Element<any>,
renderScrollComponent?: (props: Object) => React.Element<any>,
/**
/**
* Amount of time between low-pri item render batches, e.g. for rendering items quite a ways off
* Amount of time between low-pri item render batches, e.g. for rendering items quite a ways off
* screen. Similar fill rate/responsiveness tradeoff as `maxToRenderPerBatch`.
* screen. Similar fill rate/responsiveness tradeoff as `maxToRenderPerBatch`.
*/
*/
updateCellsBatchingPeriod: number,
updateCellsBatchingPeriod: number,
/**
/**
* See `ViewabilityHelper` for flow type and further documentation.
* See `ViewabilityHelper` for flow type and further documentation.
*/
*/
viewabilityConfig?: ViewabilityConfig,
viewabilityConfig?: ViewabilityConfig,
/**
/**
* List of ViewabilityConfig/onViewableItemsChanged pairs. A specific onViewableItemsChanged
* List of ViewabilityConfig/onViewableItemsChanged pairs. A specific onViewableItemsChanged
* will be called when its corresponding ViewabilityConfig's conditions are met.
* will be called when its corresponding ViewabilityConfig's conditions are met.
*/
*/
viewabilityConfigCallbackPairs?: Array<ViewabilityConfigCallbackPair>,
viewabilityConfigCallbackPairs?: Array<ViewabilityConfigCallbackPair>,
/**
/**
* Determines the maximum number of items rendered outside of the visible area, in units of
* Determines the maximum number of items rendered outside of the visible area, in units of
* visible lengths. So if your list fills the screen, then `windowSize={21}` (the default) will
* visible lengths. So if your list fills the screen, then `windowSize={21}` (the default) will
* render the visible screen area plus up to 10 screens above and 10 below the viewport. Reducing
* render the visible screen area plus up to 10 screens above and 10 below the viewport. Reducing
* this number will reduce memory consumption and may improve performance, but will increase the
* this number will reduce memory consumption and may improve performance, but will increase the
* chance that fast scrolling may reveal momentary blank areas of unrendered content.
* chance that fast scrolling may reveal momentary blank areas of unrendered content.
*/
*/
windowSize: number,
windowSize: number,
/**
/**
* The legacy implementation is no longer supported.
* The legacy implementation is no longer supported.
*/
*/
legacyImplementation?: empty,
legacyImplementation?: empty,
|};
|};


type Props = {|
type Props = {|
...React.ElementConfig<typeof ScrollView>,
...React.ElementConfig<typeof ScrollView>,
...RequiredProps,
...RequiredProps,
...OptionalProps,
...OptionalProps,
|};
|};


type DefaultProps = {|
type DefaultProps = {|
disableVirtualization: boolean,
disableVirtualization: boolean,
horizontal: boolean,
horizontal: boolean,
initialNumToRender: number,
initialNumToRender: number,
keyExtractor: (item: Item, index: number) => string,
keyExtractor: (item: Item, index: number) => string,
maxToRenderPerBatch: number,
maxToRenderPerBatch: number,
onEndReachedThreshold: number,
onEndReachedThreshold: number,
scrollEventThrottle: number,
scrollEventThrottle: number,
updateCellsBatchingPeriod: number,
updateCellsBatchingPeriod: number,
windowSize: number,
windowSize: number,
|};
|};


let _usedIndexForKey = false;
let _usedIndexForKey = false;
let _keylessItemComponentName: string = '';
let _keylessItemComponentName: string = '';


type Frame = {
offset: number,
length: number,
index: number,
inLayout: boolean,
...
};

type ChildListState = {
first: number,
last: number,
frames: {[key: number]: Frame, ...},
...
};

type State = {
type State = {
first: number,
first: number,
last: number,
last: number,
...
};

// Data propagated through nested lists (regardless of orientation) that is
// useful for producing diagnostics for usage errors involving nesting (e.g
// missing/duplicate keys).
type ListDebugInfo = {
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,
};
};


/**
/**
* Base implementation for the more convenient [`<FlatList>`](https://reactnative.dev/docs/flatlist.html)
* Base implementation for the more convenient [`<FlatList>`](https://reactnative.dev/docs/flatlist.html)
* and [`<SectionList>`](https://reactnative.dev/docs/sectionlist.html) components, which are also better
* and [`<SectionList>`](https://reactnative.dev/docs/sectionlist.html) components, which are also better
* documented. In general, this should only really be used if you need more flexibility than
* documented. In general, this should only really be used if you need more flexibility than
* `FlatList` provides, e.g. for use with immutable data instead of plain arrays.
* `FlatList` provides, e.g. for use with immutable data instead of plain arrays.
*
*
* Virtualization massively improves memory consumption and performance of large lists by
* Virtualization massively improves memory consumption and performance of large lists by
* maintaining a finite render window of active items and replacing all items outside of the render
* maintaining a finite render window of active items and replacing all items outside of the render
* window with appropriately sized blank space. The window adapts to scrolling behavior, and items
* window with appropriately sized blank space. The window adapts to scrolling behavior, and items
* are rendered incrementally with low-pri (after any running interactions) if they are far from the
* are rendered incrementally with low-pri (after any running interactions) if they are far from the
* visible area, or with hi-pri otherwise to minimize the potential of seeing blank space.
* visible area, or with hi-pri otherwise to minimize the potential of seeing blank space.
*
*
* Some caveats:
* Some caveats:
*
*
* - Internal state is not preserved when content scrolls out of the render window. Make sure all
* - Internal state is not preserved when content scrolls out of the render window. Make sure all
* your data is captured in the item data or external stores like Flux, Redux, or Relay.
* your data is captured in the item data or external stores like Flux, Redux, or Relay.
* - This is a `PureComponent` which means that it will not re-render if `props` remain shallow-
* - This is a `PureComponent` which means that it will not re-render if `props` remain shallow-
* equal. Make sure that everything your `renderItem` function depends on is passed as a prop
* equal. Make sure that everything your `renderItem` function depends on is passed as a prop
* (e.g. `extraData`) that is not `===` after updates, otherwise your UI may not update on
* (e.g. `extraData`) that is not `===` after updates, otherwise your UI may not update on
* changes. This includes the `data` prop and parent component state.
* changes. This includes the `data` prop and parent component state.
* - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously
* - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously
* offscreen. This means it's possible to scroll faster than the fill rate ands momentarily see
* offscreen. This means it's possible to scroll faster than the fill rate ands momentarily see
* blank content. This is a tradeoff that can be adjusted to suit the needs of each application,
* blank content. This is a tradeoff that can be adjusted to suit the needs of each application,
* and we are working on improving it behind the scenes.
* and we are working on improving it behind the scenes.
* - By default, the list looks for a `key` or `id` prop on each item and uses that for the React key.
* - By default, the list looks for a `key` or `id` prop on each item and uses that for the React key.
* Alternatively, you can provide a custom `keyExtractor` prop.
* Alternatively, you can provide a custom `keyExtractor` prop.
*
*
*/
*/
class VirtualizedList extends React.PureComponent<Props, State> {
class VirtualizedList extends React.PureComponent<Props, State> {
props: Props;
static contextType: typeof VirtualizedListContext = VirtualizedListContext;


// scrollToEnd may be janky without getItemLayout prop
// scrollToEnd may be janky without getItemLayout prop
scrollToEnd(params?: ?{animated?: ?boolean, ...}) {
scrollToEnd(params?: ?{animated?: ?boolean, ...}) {
const animated = params ? params.animated : true;
const animated = params ? params.animated : true;
const veryLast = this.props.getItemCount(this.props.data) - 1;
const veryLast = this.props.getItemCount(this.props.data) - 1;
const frame = this._getFrameMetricsApprox(veryLast);
const frame = this._getFrameMetricsApprox(veryLast);
const offset = Math.max(
const offset = Math.max(
0,
0,
frame.offset +
frame.offset +
frame.length +
frame.length +
this._footerLength -
this._footerLength -
this._scrollMetrics.visibleLength,
this._scrollMetrics.visibleLength,
);
);


if (this._scrollRef == null) {
if (this._scrollRef == null) {
return;
return;
}
}


if (this._scrollRef.scrollTo == null) {
console.warn(
'No scrollTo method provided. This may be because you have two nested ' +
'VirtualizedLists with the same orientation, or because you are ' +
'using a custom component that does not implement scrollTo.',
);
return;
}

this._scrollRef.scrollTo(
this._scrollRef.scrollTo(
this.props.horizontal ? {x: offset, animated} : {y: offset, animated},
this.props.horizontal ? {x: offset, animated} : {y: offset, animated},
);
);
}
}


// scrollToIndex may be janky without getItemLayout prop
// scrollToIndex may be janky without getItemLayout prop
scrollToIndex(params: {
scrollToIndex(params: {
animated?: ?boolean,
animated?: ?boolean,
index: number,
index: number,
viewOffset?: number,
viewOffset?: number,
viewPosition?: number,
viewPosition?: number,
...
...
}) {
}) {
const {
const {
data,
data,
horizontal,
horizontal,
getItemCount,
getItemCount,
getItemLayout,
getItemLayout,
onScrollToIndexFailed,
onScrollToIndexFailed,
} = this.props;
} = this.props;
const {animated, index, viewOffset, viewPosition} = params;
const {animated, index, viewOffset, viewPosition} = params;
invariant(
invariant(
index >= 0 && index < getItemCount(data),
index >= 0,
`scrollToIndex out of range: requested index ${index} but maximum is ${getItemCount(
`scrollToIndex out of range: requested index ${index} but minimum is 0`,
);
invariant(
getItemCount(data) >= 1,
`scrollToIndex out of range: item length ${getItemCount(
data,
)} but minimum is 1`,
);
invariant(
index < getItemCount(data),
`scrollToIndex out of range: requested index ${index} is out of 0 to ${getItemCount(
data,
data,
) - 1}`,
) - 1}`,
);
);
if (!getItemLayout && index > this._highestMeasuredFrameIndex) {
if (!getItemLayout && index > this._highestMeasuredFrameIndex) {
invariant(
invariant(
!!onScrollToIndexFailed,
!!onScrollToIndexFailed,
'scrollToIndex should be used in conjunction with getItemLayout or onScrollToIndexFailed, ' +
'scrollToIndex should be used in conjunction with getItemLayout or onScrollToIndexFailed, ' +
'otherwise there is no way to know the location of offscreen indices or handle failures.',
'otherwise there is no way to know the location of offscreen indices or handle failures.',
);
);
onScrollToIndexFailed({
onScrollToIndexFailed({
averageItemLength: this._averageCellLength,
averageItemLength: this._averageCellLength,
highestMeasuredFrameIndex: this._highestMeasuredFrameIndex,
highestMeasuredFrameIndex: this._highestMeasuredFrameIndex,
index,
index,
});
});
return;
return;
}
}
const frame = this._getFrameMetricsApprox(index);
const frame = this._getFrameMetricsApprox(index);
const offset =
const offset =
Math.max(
Math.max(
0,
0,
frame.offset -
frame.offset -
(viewPosition || 0) *
(viewPosition || 0) *
(this._scrollMetrics.visibleLength - frame.length),
(this._scrollMetrics.visibleLength - frame.length),
) - (viewOffset || 0);
) - (viewOffset || 0);


if (this._scrollRef == null) {
if (this._scrollRef == null) {
return;
return;
}
}


if (this._scrollRef.scrollTo == null) {
console.warn(
'No scrollTo method provided. This may be because you have two nested ' +
'VirtualizedLists with the same orientation, or because you are ' +
'using a custom component that does not implement scrollTo.',
);
return;
}

this._scrollRef.scrollTo(
this._scrollRef.scrollTo(
horizontal ? {x: offset, animated} : {y: offset, animated},
horizontal ? {x: offset, animated} : {y: offset, animated},
);
);
}
}


// scrollToItem may be janky without getItemLayout prop. Required linear scan through items -
// scrollToItem may be janky without getItemLayout prop. Required linear scan through items -
// use scrollToIndex instead if possible.
// use scrollToIndex instead if possible.
scrollToItem(params: {
scrollToItem(params: {
animated?: ?boolean,
animated?: ?boolean,
item: Item,
item: Item,
viewPosition?: number,
viewPosition?: number,
...
...
}) {
}) {
const {item} = params;
const {item} = params;
const {data, getItem, getItemCount} = this.props;
const {data, getItem, getItemCount} = this.props;
const itemCount = getItemCount(data);
const itemCount = getItemCount(data);
for (let index = 0; index < itemCount; index++) {
for (let index = 0; index < itemCount; index++) {
if (getItem(data, index) === item) {
if (getItem(data, index) === item) {
this.scrollToIndex({...params, index});
this.scrollToIndex({...params, index});
break;
break;
}
}
}
}
}
}


/**
/**
* Scroll to a specific content pixel offset in the list.
* Scroll to a specific content pixel offset in the list.
*
*
* Param `offset` expects the offset to scroll to.
* Param `offset` expects the offset to scroll to.
* In case of `horizontal` is true, the offset is the x-value,
* In case of `horizontal` is true, the offset is the x-value,
* in any other case the offset is the y-value.
* in any other case the offset is the y-value.
*
*
* Param `animated` (`true` by default) defines whether the list
* Param `animated` (`true` by default) defines whether the list
* should do an animation while scrolling.
* should do an animation while scrolling.
*/
*/
scrollToOffset(params: {animated?: ?boolean, offset: number, ...}) {
scrollToOffset(params: {animated?: ?boolean, offset: number, ...}) {
const {animated, offset} = params;
const {animated, offset} = params;


if (this._scrollRef == null) {
if (this._scrollRef == null) {
return;
return;
}
}


if (this._scrollRef.scrollTo == null) {
console.warn(
'No scrollTo method provided. This may be because you have two nested ' +
'VirtualizedLists with the same orientation, or because you are ' +
'using a custom component that does not implement scrollTo.',
);
return;
}

this._scrollRef.scrollTo(
this._scrollRef.scrollTo(
this.props.horizontal ? {x: offset, animated} : {y: offset, animated},
this.props.horizontal ? {x: offset, animated} : {y: offset, animated},
);
);
}
}


recordInteraction() {
recordInteraction() {
this._nestedChildLists.forEach(childList => {
this._nestedChildLists.forEach(childList => {
childList.ref && childList.ref.recordInteraction();
childList.ref && childList.ref.recordInteraction();
});
});
this._viewabilityTuples.forEach(t => {
this._viewabilityTuples.forEach(t => {
t.viewabilityHelper.recordInteraction();
t.viewabilityHelper.recordInteraction();
});
});
this._updateViewableItems(this.props.data);
this._updateViewableItems(this.props.data);
}
}


flashScrollIndicators() {
flashScrollIndicators() {
if (this._scrollRef == null) {
if (this._scrollRef == null) {
return;
return;
}
}


this._scrollRef.flashScrollIndicators();
this._scrollRef.flashScrollIndicators();
}
}


/**
/**
* Provides a handle to the underlying scroll responder.
* Provides a handle to the underlying scroll responder.
* Note that `this._scrollRef` might not be a `ScrollView`, so we
* Note that `this._scrollRef` might not be a `ScrollView`, so we
* need to check that it responds to `getScrollResponder` before calling it.
* need to check that it responds to `getScrollResponder` before calling it.
*/
*/
getScrollResponder(): ?ScrollResponderType {
getScrollResponder(): ?ScrollResponderType {
if (this._scrollRef && this._scrollRef.getScrollResponder) {
if (this._scrollRef && this._scrollRef.getScrollResponder) {
return this._scrollRef.getScrollResponder();
return this._scrollRef.getScrollResponder();
}
}
}
}


getScrollableNode(): ?number {
getScrollableNode(): ?number {
if (this._scrollRef && this._scrollRef.getScrollableNode) {
if (this._scrollRef && this._scrollRef.getScrollableNode) {
return this._scrollRef.getScrollableNode();
return this._scrollRef.getScrollableNode();
} else {
} else {
return ReactNative.findNodeHandle(this._scrollRef);
return ReactNative.findNodeHandle(this._scrollRef);
}
}
}
}


getScrollRef():
getScrollRef():
| ?React.ElementRef<typeof ScrollView>
| ?React.ElementRef<typeof ScrollView>
| ?React.ElementRef<typeof View> {
| ?React.ElementRef<typeof View> {
if (this._scrollRef && this._scrollRef.getScrollRef) {
if (this._scrollRef && this._scrollRef.getScrollRef) {
return this._scrollRef.getScrollRef();
return this._scrollRef.getScrollRef();
} else {
} else {
return this._scrollRef;
return this._scrollRef;
}
}
}
}


setNativeProps(props: Object) {
setNativeProps(props: Object) {
if (this._scrollRef) {
if (this._scrollRef) {
this._scrollRef.setNativeProps(props);
this._scrollRef.setNativeProps(props);
}
}
}
}


static defaultProps: DefaultProps = {
static defaultProps: DefaultProps = {
disableVirtualization: false,
disableVirtualization: false,
horizontal: false,
horizontal: false,
initialNumToRender: 10,
initialNumToRender: 10,
keyExtractor: (item: Item, index: number) => {
keyExtractor: (item: Item, index: number) => {
if (item.key != null) {
if (item.key != null) {
return item.key;
return item.key;
}
}
if (item.id != null) {
if (item.id != null) {
return item.id;
return item.id;
}
}
_usedIndexForKey = true;
_usedIndexForKey = true;
if (item.type && item.type.displayName) {
if (item.type && item.type.displayName) {
_keylessItemComponentName = item.type.displayName;
_keylessItemComponentName = item.type.displayName;
}
}
return String(index);
return String(index);
},
},
maxToRenderPerBatch: 10,
maxToRenderPerBatch: 10,
onEndReachedThreshold: 2, // multiples of length
onEndReachedThreshold: 2, // multiples of length
scrollEventThrottle: 50,
scrollEventThrottle: 50,
updateCellsBatchingPeriod: 50,
updateCellsBatchingPeriod: 50,
windowSize: 21, // multiples of length
windowSize: 21, // multiples of length
};
};


static contextTypes:
| any
| {|
virtualizedCell: {|
cellKey: React$PropType$Primitive<string>,
|},
virtualizedList: {|
getScrollMetrics: React$PropType$Primitive<Function>,
horizontal: React$PropType$Primitive<boolean>,
getOutermostParentListRef: React$PropType$Primitive<Function>,
getNestedChildState: React$PropType$Primitive<Function>,
registerAsNestedChild: React$PropType$Primitive<Function>,
unregisterAsNestedChild: React$PropType$Primitive<Function>,
debugInfo: {|
listKey: React$PropType$Primitive<string>,
cellKey: React$PropType$Primitive<string>,
|},
|},
|} = {
virtualizedCell: PropTypes.shape({
cellKey: PropTypes.string,
}),
virtualizedList: PropTypes.shape({
getScrollMetrics: PropTypes.func,
horizontal: PropTypes.bool,
getOutermostParentListRef: PropTypes.func,
getNestedChildState: PropTypes.func,
registerAsNestedChild: PropTypes.func,
unregisterAsNestedChild: PropTypes.func,
debugInfo: PropTypes.shape({
listKey: PropTypes.string,
cellKey: PropTypes.string,
}),
}),
};

static childContextTypes:
| any
| {|
getScrollMetrics: React$PropType$Primitive<Function>,
horizontal: React$PropType$Primitive<boolean>,
getOutermostParentListRef: React$PropType$Primitive<Function>,
getNestedChildState: React$PropType$Primitive<Function>,
registerAsNestedChild: React$PropType$Primitive<Function>,
unregisterAsNestedChild: React$PropType$Primitive<Function>,
|} = {
virtualizedList: PropTypes.shape({
getScrollMetrics: PropTypes.func,
horizontal: PropTypes.bool,
getOutermostParentListRef: PropTypes.func,
getNestedChildState: PropTypes.func,
registerAsNestedChild: PropTypes.func,
unregisterAsNestedChild: PropTypes.func,
}),
};

getChildContext(): {|
virtualizedList: {
getScrollMetrics: () => {
contentLength: number,
dOffset: number,
dt: number,
offset: number,
timestamp: number,
velocity: number,
visibleLength: number,
...
},
horizontal: ?boolean,
getOutermostParentListRef: Function,
getNestedChildState: string => ?ChildListState,
registerAsNestedChild: ({
cellKey: string,
key: string,
ref: VirtualizedList,
parentDebugInfo: ListDebugInfo,
...
}) => ?ChildListState,
unregisterAsNestedChild: ({
key: string,
state: ChildListState,
...
}) => void,
debugInfo: ListDebugInfo,
...
},
|} {
return {
virtualizedList: {
getScrollMetrics: this._getScrollMetrics,
horizontal: this.props.horizontal,
getOutermostParentListRef: this._getOutermostParentListRef,
getNestedChildState: this._getNestedChildState,
registerAsNestedChild: this._registerAsNestedChild,
unregisterAsNestedChild: this._unregisterAsNestedChild,
debugInfo: this._getDebugInfo(),
},
};
}

_getCellKey(): string {
_getCellKey(): string {
return (
return this.context?.cellKey || 'rootList';
(this.context.virtualizedCell && this.context.virtualizedCell.cellKey) ||
'rootList'
);
}
}


_getListKey(): string {
_getListKey(): string {
return this.props.listKey || this._getCellKey();
return this.props.listKey || this._getCellKey();
}
}


_getDebugInfo(): ListDebugInfo {
_getDebugInfo(): ListDebugInfo {
return {
return {
listKey: this._getListKey(),
listKey: this._getListKey(),
cellKey: this._getCellKey(),
cellKey: this._getCellKey(),
horizontal: !!this.props.horizontal,
horizontal: !!this.props.horizontal,
parent: this.context.virtualizedList
parent: this.context?.debugInfo,
? this.context.virtualizedList.debugInfo
: null,
};
};
}
}


_getScrollMetrics = () => {
_getScrollMetrics = () => {
return this._scrollMetrics;
return this._scrollMetrics;
};
};


hasMore(): boolean {
hasMore(): boolean {
return this._hasMore;
return this._hasMore;
}
}


_getOutermostParentListRef = () => {
_getOutermostParentListRef = () => {
if (this._isNestedWithSameOrientation()) {
if (this._isNestedWithSameOrientation()) {
return this.context.virtualizedList.getOutermostParentListRef();
return this.context.getOutermostParentListRef();
} else {
} else {
return this;
return this;
}
}
};
};


_getNestedChildState = (key: string): ?ChildListState => {
_getNestedChildState = (key: string): ?ChildListState => {
const existingChildData = this._nestedChildLists.get(key);
const existingChildData = this._nestedChildLists.get(key);
return existingChildData && existingChildData.state;
return existingChildData && existingChildData.state;
};
};


_registerAsNestedChild = (childList: {
_registerAsNestedChild = (childList: {
cellKey: string,
cellKey: string,
key: string,
key: string,
ref: VirtualizedList,
ref: VirtualizedList,
parentDebugInfo: ListDebugInfo,
parentDebugInfo: ListDebugInfo,
...
...
}): ?ChildListState => {
}): ?ChildListState => {
// Register the mapping between this child key and the cellKey for its cell
// Register the mapping between this child key and the cellKey for its cell
const childListsInCell =
const childListsInCell =
this._cellKeysToChildListKeys.get(childList.cellKey) || new Set();
this._cellKeysToChildListKeys.get(childList.cellKey) || new Set();
childListsInCell.add(childList.key);
childListsInCell.add(childList.key);
this._cellKeysToChildListKeys.set(childList.cellKey, childListsInCell);
this._cellKeysToChildListKeys.set(childList.cellKey, childListsInCell);
const existingChildData = this._nestedChildLists.get(childList.key);
const existingChildData = this._nestedChildLists.get(childList.key);
if (existingChildData && existingChildData.ref !== null) {
if (existingChildData && existingChildData.ref !== null) {
console.error(
console.error(
'A VirtualizedList contains a cell which itself contains ' +
'A VirtualizedList contains a cell which itself contains ' +
'more than one VirtualizedList of the same orientation as the parent ' +
'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' +
'list. You must pass a unique listKey prop to each sibling list.\n\n' +
describeNestedLists({
describeNestedLists({
...childList,
...childList,
// We're called from the child's componentDidMount, so it's safe to
// We're called from the child's componentDidMount, so it's safe to
// read the child's props here (albeit weird).
// read the child's props here (albeit weird).
horizontal: !!childList.ref.props.horiz
horizontal: !!childList.ref.props.horizontal,
}),
);
}
this._nestedChildLists.set(childList.key, {
ref: childList.ref,
state: null,
});

if (this._hasInteracted) {
childList.ref.recordInteraction();
}
};

_unregisterAsNestedChild = (childList: {
key: string,
state: ChildListState,
...
}): void => {
this._nestedChildLists.set(childList.key, {
ref: null,
state: childList.state,
});
};

state: State;

constructor(props: Props) {
super(props);
invariant(
// $FlowFixMe
!props.onScroll || !props.onScroll.__isNative,
'Components based on VirtualizedList must be wrapped with Animated.createAnimatedComponent ' +
'to support native onScroll events with useNativeDriver',
);

invariant(
props.windowSize > 0,
'VirtualizedList: The windowSize prop must be present and set to a value greater than 0.',
);

this._fillRateHelper = new FillRateHelper(this._getFrameMetrics);
this._updateCellsToRenderBatcher = new Batchinator(
this._updateCellsToRender,
this.props.updateCellsBatchingPeriod,
);

if (this.props.viewabilityConfigCallbackPairs) {
this._viewabilityTuples = this.props.viewabilityConfigCallbackPairs.map(
pair => ({
viewabilityHelper: new ViewabilityHelper(pair.viewabilityConfig),
onViewableItemsChanged: pair.onViewableItemsChanged,
}),
);
} else if (this.props.onViewableItemsChanged) {
this._viewabilityTuples.push({
viewabilityHelper: new ViewabilityHelper(this.props.viewabilityConfig),
onViewableItemsChanged: this.props.onViewableItemsChanged,
});
}

let initialState = {
first: this.props.initialScrollIndex || 0,
last:
Math.min(
this.props.getItemCount(this.props.data),
(this.props.initialScrollIndex || 0) + this.props.initialNumToRender,
) - 1,
};

if (this._isNestedWithSameOrientation()) {
const storedState = this.context.getNestedChildState(this._getListKey());
if (storedState) {
initialState = storedState;
this.state = storedState;
this._frames = storedState.frames;
}
}

this.state = initialState;
}

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 i