@@ -326,6 +326,11 @@ let _keylessItemComponentName: string = '';
326
326
type State = {
327
327
first : number ,
328
328
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 ,
329
334
} ;
330
335
331
336
/**
@@ -373,6 +378,40 @@ function windowSizeOrDefault(windowSize: ?number) {
373
378
return windowSize ?? 21 ;
374
379
}
375
380
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
+
376
415
/**
377
416
* Base implementation for the more convenient [`<FlatList>`](https://reactnative.dev/docs/flatlist)
378
417
* and [`<SectionList>`](https://reactnative.dev/docs/sectionlist) components, which are also better
@@ -755,6 +794,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
755
794
( this . props . initialScrollIndex || 0 ) +
756
795
initialNumToRenderOrDefault ( this . props . initialNumToRender ) ,
757
796
) - 1 ,
797
+ firstItemKey : getItemKey ( this . props , 0 ) ,
798
+ maintainVisibleContentPositionAdjustment : null ,
758
799
} ;
759
800
760
801
if ( this . _isNestedWithSameOrientation ( ) ) {
@@ -845,10 +886,27 @@ class VirtualizedList extends React.PureComponent<Props, State> {
845
886
}
846
887
847
888
static getDerivedStateFromProps ( newProps : Props , prevState : State ) : State {
848
- const { data, getItemCount} = newProps ;
889
+ const { data, getItemCount, maintainVisibleContentPosition} = newProps ;
890
+ const { firstItemKey : prevFirstItemKey } = prevState ;
849
891
const maxToRenderPerBatch = maxToRenderPerBatchOrDefault (
850
892
newProps . maxToRenderPerBatch ,
851
893
) ;
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
+
852
910
// first and last could be stale (e.g. if a new, shorter items props is passed in), so we make
853
911
// sure we're rendering a reasonable range here.
854
912
return {
@@ -857,6 +915,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
857
915
Math . min ( prevState . first , getItemCount ( data ) - 1 - maxToRenderPerBatch ) ,
858
916
) ,
859
917
last : Math . max ( 0 , Math . min ( prevState . last , getItemCount ( data ) - 1 ) ) ,
918
+ firstItemKey : newFirstItemKey ,
919
+ maintainVisibleContentPositionAdjustment,
860
920
} ;
861
921
}
862
922
@@ -882,7 +942,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
882
942
last = Math . min ( end , last ) ;
883
943
for ( let ii = first ; ii <= last ; ii ++ ) {
884
944
const item = getItem ( data , ii ) ;
885
- const key = this . _keyExtractor ( item , ii ) ;
945
+ const key = extractKey ( this . props , item , ii ) ;
886
946
this . _indicesToKeys . set ( ii , key ) ;
887
947
if ( stickyIndicesFromProps . has ( ii + stickyOffset ) ) {
888
948
this . pushOrUnshift ( stickyHeaderIndices , cells . length ) ;
@@ -934,21 +994,6 @@ class VirtualizedList extends React.PureComponent<Props, State> {
934
994
_getSpacerKey = ( isVertical : boolean ) : string =>
935
995
isVertical ? 'height' : 'width' ;
936
996
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
-
952
997
render ( ) : React . Node {
953
998
if ( __DEV__ ) {
954
999
const flatStyles = flattenStyle ( this . props . contentContainerStyle ) ;
@@ -1008,7 +1053,11 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1008
1053
const lastInitialIndex = this . props . initialScrollIndex
1009
1054
? - 1
1010
1055
: 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
+ }
1012
1061
this . _pushCells (
1013
1062
cells ,
1014
1063
stickyHeaderIndices ,
@@ -1599,6 +1648,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1599
1648
onEndReachedThreshold,
1600
1649
initialScrollIndex,
1601
1650
} = this . props ;
1651
+ if ( this . state . maintainVisibleContentPositionAdjustment != null ) {
1652
+ return ;
1653
+ }
1602
1654
const { contentLength , visibleLength , offset } = this._scrollMetrics;
1603
1655
const distanceFromStart = offset;
1604
1656
const distanceFromEnd = contentLength - visibleLength - offset;
@@ -1756,6 +1808,17 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1756
1808
velocity,
1757
1809
visibleLength,
1758
1810
} ;
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
+ }
1759
1822
this . _updateViewableItems ( this . props . data ) ;
1760
1823
if ( ! this . props ) {
1761
1824
return ;
@@ -1769,7 +1832,10 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1769
1832
} ;
1770
1833
1771
1834
_scheduleCellsToRenderUpdate ( ) {
1772
- const { first , last } = this.state;
1835
+ const { first , last, maintainVisibleContentPositionAdjustment} = this . state ;
1836
+ if ( maintainVisibleContentPositionAdjustment != null ) {
1837
+ return ;
1838
+ }
1773
1839
const { offset , visibleLength , velocity } = this . _scrollMetrics ;
1774
1840
const itemCount = this . props . getItemCount ( this . props . data ) ;
1775
1841
let hiPri = false ;
@@ -1967,7 +2033,12 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1967
2033
_createViewToken = ( index : number , isViewable : boolean ) => {
1968
2034
const { data, getItem} = this . props ;
1969
2035
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
+ } ;
1971
2042
} ;
1972
2043
1973
2044
_getFrameMetricsApprox = (
@@ -2009,7 +2080,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
2009
2080
'Tried to get frame for out of range index ' + index ,
2010
2081
) ;
2011
2082
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 ) ] ;
2013
2084
if ( ! frame || frame . index !== index ) {
2014
2085
if ( getItemLayout ) {
2015
2086
frame = getItemLayout ( data , index ) ;
@@ -2213,8 +2284,8 @@ class CellRenderer extends React.Component<
2213
2284
: inversionStyle ;
2214
2285
const result = ! CellRendererComponent ? (
2215
2286
/* $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. */
2218
2289
< View style = { cellStyle } onLayout = { onLayout } >
2219
2290
{ element }
2220
2291
{ itemSeparator }
0 commit comments