Skip to content

Commit 859a257

Browse files
committed
Apply maintainVisibleContentPosition fix
1 parent 71246a7 commit 859a257

File tree

1 file changed

+94
-23
lines changed
  • packages/react-native-web/src/vendor/react-native/VirtualizedList

1 file changed

+94
-23
lines changed

packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js

Lines changed: 94 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,11 @@ let _keylessItemComponentName: string = '';
326326
type State = {
327327
first: number,
328328
last: number,
329+
// Used to track items added at the start of the list for maintainVisibleContentPosition.
330+
firstItemKey: ?string,
331+
// When using maintainVisibleContentPosition we need to adjust the window to make sure
332+
// make sure that the visible elements are still rendered.
333+
maintainVisibleContentPositionAdjustment: ?number,
329334
};
330335

331336
/**
@@ -373,6 +378,40 @@ function windowSizeOrDefault(windowSize: ?number) {
373378
return windowSize ?? 21;
374379
}
375380

381+
function extractKey(props: Props, item: Item, index: number): string {
382+
if (props.keyExtractor != null) {
383+
return props.keyExtractor(item, index);
384+
}
385+
386+
const key = defaultKeyExtractor(item, index);
387+
if (key === String(index)) {
388+
_usedIndexForKey = true;
389+
if (item.type && item.type.displayName) {
390+
_keylessItemComponentName = item.type.displayName;
391+
}
392+
}
393+
return key;
394+
}
395+
396+
function findItemIndexWithKey(props: Props, key: string): ?number {
397+
for (let ii = 0; ii < props.getItemCount(props.data); ii++) {
398+
const item = props.getItem(props.data, ii);
399+
const curKey = extractKey(props, item, ii);
400+
if (curKey === key) {
401+
return ii;
402+
}
403+
}
404+
return null;
405+
}
406+
407+
function getItemKey(props: Props, index: number): ?string {
408+
const item = props.getItem(props.data, index);
409+
if (item == null) {
410+
return null;
411+
}
412+
return extractKey(props, item, 0);
413+
}
414+
376415
/**
377416
* Base implementation for the more convenient [`<FlatList>`](https://reactnative.dev/docs/flatlist)
378417
* and [`<SectionList>`](https://reactnative.dev/docs/sectionlist) components, which are also better
@@ -755,6 +794,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
755794
(this.props.initialScrollIndex || 0) +
756795
initialNumToRenderOrDefault(this.props.initialNumToRender),
757796
) - 1,
797+
firstItemKey: getItemKey(this.props, 0),
798+
maintainVisibleContentPositionAdjustment: null,
758799
};
759800

760801
if (this._isNestedWithSameOrientation()) {
@@ -845,10 +886,27 @@ class VirtualizedList extends React.PureComponent<Props, State> {
845886
}
846887

847888
static getDerivedStateFromProps(newProps: Props, prevState: State): State {
848-
const {data, getItemCount} = newProps;
889+
const {data, getItemCount, maintainVisibleContentPosition} = newProps;
890+
const {firstItemKey: prevFirstItemKey} = prevState;
849891
const maxToRenderPerBatch = maxToRenderPerBatchOrDefault(
850892
newProps.maxToRenderPerBatch,
851893
);
894+
895+
let maintainVisibleContentPositionAdjustment =
896+
prevState.maintainVisibleContentPositionAdjustment;
897+
const newFirstItemKey = getItemKey(newProps, 0);
898+
if (
899+
maintainVisibleContentPosition != null &&
900+
maintainVisibleContentPositionAdjustment == null &&
901+
prevFirstItemKey != null &&
902+
newFirstItemKey != null
903+
) {
904+
maintainVisibleContentPositionAdjustment =
905+
newFirstItemKey !== prevFirstItemKey
906+
? findItemIndexWithKey(newProps, prevFirstItemKey)
907+
: null;
908+
}
909+
852910
// first and last could be stale (e.g. if a new, shorter items props is passed in), so we make
853911
// sure we're rendering a reasonable range here.
854912
return {
@@ -857,6 +915,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
857915
Math.min(prevState.first, getItemCount(data) - 1 - maxToRenderPerBatch),
858916
),
859917
last: Math.max(0, Math.min(prevState.last, getItemCount(data) - 1)),
918+
firstItemKey: newFirstItemKey,
919+
maintainVisibleContentPositionAdjustment,
860920
};
861921
}
862922

@@ -882,7 +942,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
882942
last = Math.min(end, last);
883943
for (let ii = first; ii <= last; ii++) {
884944
const item = getItem(data, ii);
885-
const key = this._keyExtractor(item, ii);
945+
const key = extractKey(this.props, item, ii);
886946
this._indicesToKeys.set(ii, key);
887947
if (stickyIndicesFromProps.has(ii + stickyOffset)) {
888948
this.pushOrUnshift(stickyHeaderIndices, cells.length);
@@ -934,21 +994,6 @@ class VirtualizedList extends React.PureComponent<Props, State> {
934994
_getSpacerKey = (isVertical: boolean): string =>
935995
isVertical ? 'height' : 'width';
936996

937-
_keyExtractor(item: Item, index: number) {
938-
if (this.props.keyExtractor != null) {
939-
return this.props.keyExtractor(item, index);
940-
}
941-
942-
const key = defaultKeyExtractor(item, index);
943-
if (key === String(index)) {
944-
_usedIndexForKey = true;
945-
if (item.type && item.type.displayName) {
946-
_keylessItemComponentName = item.type.displayName;
947-
}
948-
}
949-
return key;
950-
}
951-
952997
render(): React.Node {
953998
if (__DEV__) {
954999
const flatStyles = flattenStyle(this.props.contentContainerStyle);
@@ -1008,7 +1053,11 @@ class VirtualizedList extends React.PureComponent<Props, State> {
10081053
const lastInitialIndex = this.props.initialScrollIndex
10091054
? -1
10101055
: initialNumToRenderOrDefault(this.props.initialNumToRender) - 1;
1011-
const {first, last} = this.state;
1056+
let {first, last, maintainVisibleContentPositionAdjustment} = this.state;
1057+
if (maintainVisibleContentPositionAdjustment != null) {
1058+
first += maintainVisibleContentPositionAdjustment;
1059+
last += maintainVisibleContentPositionAdjustment;
1060+
}
10121061
this._pushCells(
10131062
cells,
10141063
stickyHeaderIndices,
@@ -1599,6 +1648,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
15991648
onEndReachedThreshold,
16001649
initialScrollIndex,
16011650
} = this.props;
1651+
if (this.state.maintainVisibleContentPositionAdjustment != null) {
1652+
return;
1653+
}
16021654
const {contentLength, visibleLength, offset} = this._scrollMetrics;
16031655
const distanceFromStart = offset;
16041656
const distanceFromEnd = contentLength - visibleLength - offset;
@@ -1756,6 +1808,17 @@ class VirtualizedList extends React.PureComponent<Props, State> {
17561808
velocity,
17571809
visibleLength,
17581810
};
1811+
1812+
const {maintainVisibleContentPositionAdjustment} = this.state;
1813+
if (maintainVisibleContentPositionAdjustment != null) {
1814+
this.setState(state => ({
1815+
maintainVisibleContentPositionAdjustment: null,
1816+
// Also update state with adjusted values since previous values are used
1817+
// in computeWindowedRenderLimits.
1818+
first: state.first + maintainVisibleContentPositionAdjustment,
1819+
last: state.last + maintainVisibleContentPositionAdjustment,
1820+
}));
1821+
}
17591822
this._updateViewableItems(this.props.data);
17601823
if (!this.props) {
17611824
return;
@@ -1769,7 +1832,10 @@ class VirtualizedList extends React.PureComponent<Props, State> {
17691832
};
17701833

17711834
_scheduleCellsToRenderUpdate() {
1772-
const {first, last} = this.state;
1835+
const {first, last, maintainVisibleContentPositionAdjustment} = this.state;
1836+
if (maintainVisibleContentPositionAdjustment != null) {
1837+
return;
1838+
}
17731839
const {offset, visibleLength, velocity} = this._scrollMetrics;
17741840
const itemCount = this.props.getItemCount(this.props.data);
17751841
let hiPri = false;
@@ -1967,7 +2033,12 @@ class VirtualizedList extends React.PureComponent<Props, State> {
19672033
_createViewToken = (index: number, isViewable: boolean) => {
19682034
const {data, getItem} = this.props;
19692035
const item = getItem(data, index);
1970-
return {index, item, key: this._keyExtractor(item, index), isViewable};
2036+
return {
2037+
index,
2038+
item,
2039+
key: extractKey(this.props, item, index),
2040+
isViewable,
2041+
};
19712042
};
19722043

19732044
_getFrameMetricsApprox = (
@@ -2009,7 +2080,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
20092080
'Tried to get frame for out of range index ' + index,
20102081
);
20112082
const item = getItem(data, index);
2012-
let frame = item && this._frames[this._keyExtractor(item, index)];
2083+
let frame = item && this._frames[extractKey(this.props, item, index)];
20132084
if (!frame || frame.index !== index) {
20142085
if (getItemLayout) {
20152086
frame = getItemLayout(data, index);
@@ -2213,8 +2284,8 @@ class CellRenderer extends React.Component<
22132284
: inversionStyle;
22142285
const result = !CellRendererComponent ? (
22152286
/* $FlowFixMe[incompatible-type-arg] (>=0.89.0 site=react_native_fb) *
2216-
This comment suppresses an error found when Flow v0.89 was deployed. *
2217-
To see the error, delete this comment and run Flow. */
2287+
This comment suppresses an error found when Flow v0.89 was deployed. *
2288+
To see the error, delete this comment and run Flow. */
22182289
<View style={cellStyle} onLayout={onLayout}>
22192290
{element}
22202291
{itemSeparator}

0 commit comments

Comments
 (0)