Permalink
Browse files

Fix and optimize VirtualizedList update triggers

Summary:
- If the initial render doesn't extend past `onEndReachedThreshold` it is likely that onEndReached won't get called until scroll, which can be a bad experience if the `initialNumToRender` is very close to the viewport height. This happens because when `onContentSizeChange`, `onLayout` may not have fired yet so we don't know what the `visibleLength` is. Fix is to also call `maybeCallOnEndReached` in `_onLayout` as well.

- We have an optimization that does hi-pri render window updates when scrolling quickly and the content reaches the edge of the viewport, but there is also an important case where the user has scrolled to the end of the content and is waiting for a network response. Once the new data comes in, we want to render it ASAP because the user is waiting for it. To solve this we refactor our scheduling code into a shared function that always checks if it should be a hi-pri update instead of just in `_onScroll`.

Reviewed By: bvaughn

Differential Revision: D4975314

fbshipit-source-id: 8d64832ecbcbdbac430a08a4018d7a32b2216a85
  • Loading branch information...
sahrens authored and facebook-github-bot committed May 3, 2017
1 parent 5084e1b commit 52e50af56d601e63a0c83118c194149d3aed38ea
Showing with 33 additions and 26 deletions.
  1. +33 −26 Libraries/Lists/VirtualizedList.js
@@ -347,7 +347,6 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
if (data !== this.props.data || extraData !== this.props.extraData) {
this._hasDataChangedSinceEndReached = true;
}
this._updateCellsToRenderBatcher.schedule();
}
_pushCells(
@@ -513,7 +512,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
}
componentDidUpdate() {
this._updateCellsToRenderBatcher.schedule();
this._scheduleCellsToRenderUpdate();
}
_averageCellLength = 0;
@@ -567,7 +566,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
this._averageCellLength = this._totalCellLength / this._totalCellsMeasured;
this._frames[cellKey] = next;
this._highestMeasuredFrameIndex = Math.max(this._highestMeasuredFrameIndex, index);
this._updateCellsToRenderBatcher.schedule();
this._scheduleCellsToRenderUpdate();
} else {
this._frames[cellKey].inLayout = true;
}
@@ -584,7 +583,8 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
_onLayout = (e: Object) => {
this._scrollMetrics.visibleLength = this._selectLength(e.nativeEvent.layout);
this.props.onLayout && this.props.onLayout(e);
this._updateCellsToRenderBatcher.schedule();
this._scheduleCellsToRenderUpdate();
this._maybeCallOnEndReached();
};
_onLayoutFooter = (e) => {
@@ -671,7 +671,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
this.props.onContentSizeChange(width, height);
}
this._scrollMetrics.contentLength = this._selectLength({height, width});
this._updateCellsToRenderBatcher.schedule();
this._scheduleCellsToRenderUpdate();
this._maybeCallOnEndReached();
};
@@ -697,36 +697,43 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
const dOffset = offset - this._scrollMetrics.offset;
const velocity = dOffset / dt;
this._scrollMetrics = {contentLength, dt, dOffset, offset, timestamp, velocity, visibleLength};
const {data, getItemCount, windowSize} = this.props;
this._updateViewableItems(data);
if (!data) {
this._updateViewableItems(this.props);
if (!this.props) {
return;
}
this._maybeCallOnEndReached();
const {first, last} = this.state;
if (velocity !== 0) {
this._fillRateHelper.activate();
}
this._computeBlankness();
const itemCount = getItemCount(data);
if ((first > 0 && velocity < 0) || (last < itemCount - 1 && velocity > 0)) {
const distanceToContentEdge = Math.min(
Math.abs(this._getFrameMetricsApprox(first).offset - offset),
Math.abs(this._getFrameMetricsApprox(last).offset - (offset + visibleLength)),
this._scheduleCellsToRenderUpdate();
};
_scheduleCellsToRenderUpdate() {
const {first, last} = this.state;
const {offset, visibleLength, velocity} = this._scrollMetrics;
const itemCount = this.props.getItemCount(this.props.data);
let hiPri = false;
if (first > 0 || last < itemCount - 1) {
const distTop = offset - this._getFrameMetricsApprox(first).offset;
const distBottom = this._getFrameMetricsApprox(last).offset - (offset + visibleLength);
const scrollingThreshold = this.props.onEndReachedThreshold * visibleLength / 2;
hiPri = (
Math.min(distTop, distBottom) < 0 ||
(velocity < -2 && distTop < scrollingThreshold) ||
(velocity > 2 && distBottom < scrollingThreshold)
);
const hiPri = distanceToContentEdge < (windowSize * visibleLength / 4);
if (hiPri) {
// Don't worry about interactions when scrolling quickly; focus on filling content as fast
// as possible.
this._updateCellsToRenderBatcher.dispose({abort: true});
this._updateCellsToRender();
return;
}
}
this._updateCellsToRenderBatcher.schedule();
};
if (hiPri) {
// Don't worry about interactions when scrolling quickly; focus on filling content as fast
// as possible.
this._updateCellsToRenderBatcher.dispose({abort: true});
this._updateCellsToRender();
return;
} else {
this._updateCellsToRenderBatcher.schedule();
}
}
_onScrollBeginDrag = (e): void => {
this._viewabilityHelper.recordInteraction();

0 comments on commit 52e50af

Please sign in to comment.