@react-native/virtualized-lists 0.72.1 → 0.72.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,7 @@ export interface ViewToken {
23
23
  key: string;
24
24
  index: number | null;
25
25
  isViewable: boolean;
26
- section?: any;
26
+ section?: any | undefined;
27
27
  }
28
28
 
29
29
  export interface ViewabilityConfig {
@@ -87,6 +87,16 @@ export type ListRenderItem<ItemT> = (
87
87
  info: ListRenderItemInfo<ItemT>,
88
88
  ) => React.ReactElement | null;
89
89
 
90
+ export interface CellRendererProps<ItemT> {
91
+ cellKey: string;
92
+ children: React.ReactNode;
93
+ index: number;
94
+ item: ItemT;
95
+ onFocusCapture?: ((event: FocusEvent) => void) | undefined;
96
+ onLayout?: ((event: LayoutChangeEvent) => void) | undefined;
97
+ style: StyleProp<ViewStyle> | undefined;
98
+ }
99
+
90
100
  /**
91
101
  * @see https://reactnative.dev/docs/virtualizedlist
92
102
  */
@@ -188,7 +198,7 @@ export interface VirtualizedListWithoutRenderItemProps<ItemT>
188
198
  * The default accessor functions assume this is an Array<{key: string}> but you can override
189
199
  * getItem, getItemCount, and keyExtractor to handle any type of index-based data.
190
200
  */
191
- data?: any;
201
+ data?: any | undefined;
192
202
 
193
203
  /**
194
204
  * `debug` will turn on extra logging and visual overlays to aid with debugging both usage and
@@ -208,7 +218,7 @@ export interface VirtualizedListWithoutRenderItemProps<ItemT>
208
218
  * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the
209
219
  * `data` prop, stick it here and treat it immutably.
210
220
  */
211
- extraData?: any;
221
+ extraData?: any | undefined;
212
222
 
213
223
  /**
214
224
  * A generic accessor for extracting an item from any sort of data blob.
@@ -370,5 +380,14 @@ export interface VirtualizedListWithoutRenderItemProps<ItemT>
370
380
  */
371
381
  windowSize?: number | undefined;
372
382
 
373
- CellRendererComponent?: React.ComponentType<any> | undefined;
383
+ /**
384
+ * CellRendererComponent allows customizing how cells rendered by
385
+ * `renderItem`/`ListItemComponent` are wrapped when placed into the
386
+ * underlying ScrollView. This component must accept event handlers which
387
+ * notify VirtualizedList of changes within the cell.
388
+ */
389
+ CellRendererComponent?:
390
+ | React.ComponentType<CellRendererProps<ItemT>>
391
+ | null
392
+ | undefined;
374
393
  }
@@ -50,6 +50,7 @@ import {
50
50
  keyExtractor as defaultKeyExtractor,
51
51
  } from './VirtualizeUtils';
52
52
  import invariant from 'invariant';
53
+ import nullthrows from 'nullthrows';
53
54
  import * as React from 'react';
54
55
 
55
56
  export type {RenderItemProps, RenderItemType, Separators};
@@ -420,21 +421,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
420
421
 
421
422
  constructor(props: Props) {
422
423
  super(props);
423
- invariant(
424
- // $FlowFixMe[prop-missing]
425
- !props.onScroll || !props.onScroll.__isNative,
426
- 'Components based on VirtualizedList must be wrapped with Animated.createAnimatedComponent ' +
427
- 'to support native onScroll events with useNativeDriver',
428
- );
429
- invariant(
430
- windowSizeOrDefault(props.windowSize) > 0,
431
- 'VirtualizedList: The windowSize prop must be present and set to a value greater than 0.',
432
- );
433
-
434
- invariant(
435
- props.getItemCount,
436
- 'VirtualizedList: The "getItemCount" prop must be provided',
437
- );
424
+ this._checkProps(props);
438
425
 
439
426
  this._fillRateHelper = new FillRateHelper(this._getFrameMetrics);
440
427
  this._updateCellsToRenderBatcher = new Batchinator(
@@ -459,11 +446,6 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
459
446
  }
460
447
  }
461
448
 
462
- invariant(
463
- !this.context,
464
- 'Unexpectedly saw VirtualizedListContext available in ctor',
465
- );
466
-
467
449
  const initialRenderRegion = VirtualizedList._initialRenderRegion(props);
468
450
 
469
451
  this.state = {
@@ -472,6 +454,54 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
472
454
  };
473
455
  }
474
456
 
457
+ _checkProps(props: Props) {
458
+ const {onScroll, windowSize, getItemCount, data, initialScrollIndex} =
459
+ props;
460
+
461
+ invariant(
462
+ // $FlowFixMe[prop-missing]
463
+ !onScroll || !onScroll.__isNative,
464
+ 'Components based on VirtualizedList must be wrapped with Animated.createAnimatedComponent ' +
465
+ 'to support native onScroll events with useNativeDriver',
466
+ );
467
+ invariant(
468
+ windowSizeOrDefault(windowSize) > 0,
469
+ 'VirtualizedList: The windowSize prop must be present and set to a value greater than 0.',
470
+ );
471
+
472
+ invariant(
473
+ getItemCount,
474
+ 'VirtualizedList: The "getItemCount" prop must be provided',
475
+ );
476
+
477
+ const itemCount = getItemCount(data);
478
+
479
+ if (
480
+ initialScrollIndex != null &&
481
+ !this._hasTriggeredInitialScrollToIndex &&
482
+ (initialScrollIndex < 0 ||
483
+ (itemCount > 0 && initialScrollIndex >= itemCount)) &&
484
+ !this._hasWarned.initialScrollIndex
485
+ ) {
486
+ console.warn(
487
+ `initialScrollIndex "${initialScrollIndex}" is not valid (list has ${itemCount} items)`,
488
+ );
489
+ this._hasWarned.initialScrollIndex = true;
490
+ }
491
+
492
+ if (__DEV__ && !this._hasWarned.flexWrap) {
493
+ // $FlowFixMe[underconstrained-implicit-instantiation]
494
+ const flatStyles = StyleSheet.flatten(this.props.contentContainerStyle);
495
+ if (flatStyles != null && flatStyles.flexWrap === 'wrap') {
496
+ console.warn(
497
+ '`flexWrap: `wrap`` is not supported with the `VirtualizedList` components.' +
498
+ 'Consider using `numColumns` with `FlatList` instead.',
499
+ );
500
+ this._hasWarned.flexWrap = true;
501
+ }
502
+ }
503
+ }
504
+
475
505
  static _createRenderMask(
476
506
  props: Props,
477
507
  cellsAroundViewport: {first: number, last: number},
@@ -518,15 +548,21 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
518
548
 
519
549
  static _initialRenderRegion(props: Props): {first: number, last: number} {
520
550
  const itemCount = props.getItemCount(props.data);
521
- const scrollIndex = Math.floor(Math.max(0, props.initialScrollIndex ?? 0));
551
+
552
+ const firstCellIndex = Math.max(
553
+ 0,
554
+ Math.min(itemCount - 1, Math.floor(props.initialScrollIndex ?? 0)),
555
+ );
556
+
557
+ const lastCellIndex =
558
+ Math.min(
559
+ itemCount,
560
+ firstCellIndex + initialNumToRenderOrDefault(props.initialNumToRender),
561
+ ) - 1;
522
562
 
523
563
  return {
524
- first: scrollIndex,
525
- last:
526
- Math.min(
527
- itemCount,
528
- scrollIndex + initialNumToRenderOrDefault(props.initialNumToRender),
529
- ) - 1,
564
+ first: firstCellIndex,
565
+ last: lastCellIndex,
530
566
  };
531
567
  }
532
568
 
@@ -554,8 +590,6 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
554
590
  const onEndReachedThreshold = onEndReachedThresholdOrDefault(
555
591
  props.onEndReachedThreshold,
556
592
  );
557
- this._updateViewableItems(props, cellsAroundViewport);
558
-
559
593
  const {contentLength, offset, visibleLength} = this._scrollMetrics;
560
594
  const distanceFromEnd = contentLength - visibleLength - offset;
561
595
 
@@ -714,29 +748,31 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
714
748
  const end = getItemCount(data) - 1;
715
749
  let prevCellKey;
716
750
  last = Math.min(end, last);
751
+
717
752
  for (let ii = first; ii <= last; ii++) {
718
753
  const item = getItem(data, ii);
719
754
  const key = this._keyExtractor(item, ii, this.props);
755
+
720
756
  this._indicesToKeys.set(ii, key);
721
757
  if (stickyIndicesFromProps.has(ii + stickyOffset)) {
722
758
  stickyHeaderIndices.push(cells.length);
723
759
  }
760
+
761
+ const shouldListenForLayout =
762
+ getItemLayout == null || debug || this._fillRateHelper.enabled();
763
+
724
764
  cells.push(
725
765
  <CellRenderer
726
766
  CellRendererComponent={CellRendererComponent}
727
767
  ItemSeparatorComponent={ii < end ? ItemSeparatorComponent : undefined}
728
768
  ListItemComponent={ListItemComponent}
729
769
  cellKey={key}
730
- debug={debug}
731
- fillRateHelper={this._fillRateHelper}
732
- getItemLayout={getItemLayout}
733
770
  horizontal={horizontal}
734
771
  index={ii}
735
772
  inversionStyle={inversionStyle}
736
773
  item={item}
737
774
  key={key}
738
775
  prevCellKey={prevCellKey}
739
- onCellLayout={this._onCellLayout}
740
776
  onUpdateSeparators={this._onUpdateSeparators}
741
777
  onCellFocusCapture={e => this._onCellFocusCapture(key)}
742
778
  onUnmount={this._onCellUnmount}
@@ -744,6 +780,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
744
780
  this._cellRefs[key] = ref;
745
781
  }}
746
782
  renderItem={renderItem}
783
+ {...(shouldListenForLayout && {
784
+ onCellLayout: this._onCellLayout,
785
+ })}
747
786
  />,
748
787
  );
749
788
  prevCellKey = key;
@@ -809,16 +848,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
809
848
  }
810
849
 
811
850
  render(): React.Node {
812
- if (__DEV__) {
813
- // $FlowFixMe[underconstrained-implicit-instantiation]
814
- const flatStyles = StyleSheet.flatten(this.props.contentContainerStyle);
815
- if (flatStyles != null && flatStyles.flexWrap === 'wrap') {
816
- console.warn(
817
- '`flexWrap: `wrap`` is not supported with the `VirtualizedList` components.' +
818
- 'Consider using `numColumns` with `FlatList` instead.',
819
- );
820
- }
821
- }
851
+ this._checkProps(this.props);
822
852
  const {ListEmptyComponent, ListFooterComponent, ListHeaderComponent} =
823
853
  this.props;
824
854
  const {data, horizontal} = this.props;
@@ -1233,6 +1263,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
1233
1263
  }
1234
1264
 
1235
1265
  _onCellUnmount = (cellKey: string) => {
1266
+ delete this._cellRefs[cellKey];
1236
1267
  const curr = this._frames[cellKey];
1237
1268
  if (curr) {
1238
1269
  this._frames[cellKey] = {...curr, inLayout: false};
@@ -1509,10 +1540,17 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
1509
1540
  !this._hasTriggeredInitialScrollToIndex
1510
1541
  ) {
1511
1542
  if (this.props.contentOffset == null) {
1512
- this.scrollToIndex({
1513
- animated: false,
1514
- index: this.props.initialScrollIndex,
1515
- });
1543
+ if (
1544
+ this.props.initialScrollIndex <
1545
+ this.props.getItemCount(this.props.data)
1546
+ ) {
1547
+ this.scrollToIndex({
1548
+ animated: false,
1549
+ index: nullthrows(this.props.initialScrollIndex),
1550
+ });
1551
+ } else {
1552
+ this.scrollToEnd({animated: false});
1553
+ }
1516
1554
  }
1517
1555
  this._hasTriggeredInitialScrollToIndex = true;
1518
1556
  }
@@ -1714,6 +1752,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
1714
1752
  };
1715
1753
 
1716
1754
  _updateCellsToRender = () => {
1755
+ this._updateViewableItems(this.props, this.state.cellsAroundViewport);
1756
+
1717
1757
  this.setState((state, props) => {
1718
1758
  const cellsAroundViewport = this._adjustCellsAroundViewport(
1719
1759
  props,
@@ -1846,14 +1886,6 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
1846
1886
  const focusedCellIndex = lastFocusedCellRenderer.props.index;
1847
1887
  const itemCount = props.getItemCount(props.data);
1848
1888
 
1849
- // The cell may have been unmounted and have a stale index
1850
- if (
1851
- focusedCellIndex >= itemCount ||
1852
- this._indicesToKeys.get(focusedCellIndex) !== this._lastFocusedCellKey
1853
- ) {
1854
- return [];
1855
- }
1856
-
1857
1889
  let first = focusedCellIndex;
1858
1890
  let heightOfCellsBeforeFocused = 0;
1859
1891
  for (
@@ -13,8 +13,7 @@ import type {
13
13
  FocusEvent,
14
14
  LayoutEvent,
15
15
  } from 'react-native/Libraries/Types/CoreEventTypes';
16
- import type FillRateHelper from './FillRateHelper';
17
- import type {RenderItemType} from './VirtualizedListProps';
16
+ import type {CellRendererProps, RenderItemType} from './VirtualizedListProps';
18
17
 
19
18
  import {View, StyleSheet} from 'react-native';
20
19
  import {VirtualizedListCellContextProvider} from './VirtualizedListContext.js';
@@ -22,28 +21,17 @@ import invariant from 'invariant';
22
21
  import * as React from 'react';
23
22
 
24
23
  export type Props<ItemT> = {
25
- CellRendererComponent?: ?React.ComponentType<any>,
24
+ CellRendererComponent?: ?React.ComponentType<CellRendererProps<ItemT>>,
26
25
  ItemSeparatorComponent: ?React.ComponentType<
27
26
  any | {highlighted: boolean, leadingItem: ?ItemT},
28
27
  >,
29
28
  ListItemComponent?: ?(React.ComponentType<any> | React.Element<any>),
30
29
  cellKey: string,
31
- debug?: ?boolean,
32
- fillRateHelper: FillRateHelper,
33
- getItemLayout?: (
34
- data: any,
35
- index: number,
36
- ) => {
37
- length: number,
38
- offset: number,
39
- index: number,
40
- ...
41
- },
42
30
  horizontal: ?boolean,
43
31
  index: number,
44
32
  inversionStyle: ViewStyleProp,
45
33
  item: ItemT,
46
- onCellLayout: (event: LayoutEvent, cellKey: string, index: number) => void,
34
+ onCellLayout?: (event: LayoutEvent, cellKey: string, index: number) => void,
47
35
  onCellFocusCapture?: (event: FocusEvent) => void,
48
36
  onUnmount: (cellKey: string) => void,
49
37
  onUpdateSeparators: (
@@ -181,14 +169,13 @@ export default class CellRenderer<ItemT> extends React.Component<
181
169
  CellRendererComponent,
182
170
  ItemSeparatorComponent,
183
171
  ListItemComponent,
184
- debug,
185
- fillRateHelper,
186
- getItemLayout,
172
+ cellKey,
187
173
  horizontal,
188
174
  item,
189
175
  index,
190
176
  inversionStyle,
191
177
  onCellFocusCapture,
178
+ onCellLayout,
192
179
  renderItem,
193
180
  } = this.props;
194
181
  const element = this._renderElement(
@@ -198,11 +185,6 @@ export default class CellRenderer<ItemT> extends React.Component<
198
185
  index,
199
186
  );
200
187
 
201
- const onLayout =
202
- (getItemLayout && !debug && !fillRateHelper.enabled()) ||
203
- !this.props.onCellLayout
204
- ? undefined
205
- : this._onLayout;
206
188
  // NOTE: that when this is a sticky header, `onLayout` will get automatically extracted and
207
189
  // called explicitly by `ScrollViewStickyHeader`.
208
190
  const itemSeparator: React.Node = React.isValidElement(
@@ -224,21 +206,19 @@ export default class CellRenderer<ItemT> extends React.Component<
224
206
  const result = !CellRendererComponent ? (
225
207
  <View
226
208
  style={cellStyle}
227
- onLayout={onLayout}
228
209
  onFocusCapture={onCellFocusCapture}
229
- /* $FlowFixMe[incompatible-type-arg] (>=0.89.0 site=react_native_fb) *
230
- This comment suppresses an error found when Flow v0.89 was deployed. *
231
- To see the error, delete this comment and run Flow. */
232
- >
210
+ {...(onCellLayout && {onLayout: this._onLayout})}>
233
211
  {element}
234
212
  {itemSeparator}
235
213
  </View>
236
214
  ) : (
237
215
  <CellRendererComponent
238
- {...this.props}
216
+ cellKey={cellKey}
217
+ index={index}
218
+ item={item}
239
219
  style={cellStyle}
240
- onLayout={onLayout}
241
- onFocusCapture={onCellFocusCapture}>
220
+ onFocusCapture={onCellFocusCapture}
221
+ {...(onCellLayout && {onLayout: this._onLayout})}>
242
222
  {element}
243
223
  {itemSeparator}
244
224
  </CellRendererComponent>
@@ -9,6 +9,10 @@
9
9
  */
10
10
 
11
11
  import {typeof ScrollView} from 'react-native';
12
+ import type {
13
+ FocusEvent,
14
+ LayoutEvent,
15
+ } from 'react-native/Libraries/Types/CoreEventTypes';
12
16
  import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
13
17
  import type {
14
18
  ViewabilityConfig,
@@ -34,6 +38,16 @@ export type RenderItemProps<ItemT> = {
34
38
  ...
35
39
  };
36
40
 
41
+ export type CellRendererProps<ItemT> = $ReadOnly<{
42
+ cellKey: string,
43
+ children: React.Node,
44
+ index: number,
45
+ item: ItemT,
46
+ onFocusCapture?: (event: FocusEvent) => void,
47
+ onLayout?: (event: LayoutEvent) => void,
48
+ style: ViewStyleProp,
49
+ }>;
50
+
37
51
  export type RenderItemType<ItemT> = (
38
52
  info: RenderItemProps<ItemT>,
39
53
  ) => React.Node;
@@ -102,10 +116,12 @@ type OptionalProps = {|
102
116
  inverted?: ?boolean,
103
117
  keyExtractor?: ?(item: Item, index: number) => string,
104
118
  /**
105
- * Each cell is rendered using this element. Can be a React Component Class,
106
- * or a render function. Defaults to using View.
119
+ * CellRendererComponent allows customizing how cells rendered by
120
+ * `renderItem`/`ListItemComponent` are wrapped when placed into the
121
+ * underlying ScrollView. This component must accept event handlers which
122
+ * notify VirtualizedList of changes within the cell.
107
123
  */
108
- CellRendererComponent?: ?React.ComponentType<any>,
124
+ CellRendererComponent?: ?React.ComponentType<CellRendererProps<Item>>,
109
125
  /**
110
126
  * Rendered in between each item, but not at the top or bottom. By default, `highlighted` and
111
127
  * `leadingItem` props are provided. `renderItem` provides `separators.highlight`/`unhighlight`
package/index.js CHANGED
@@ -24,6 +24,7 @@ export type {
24
24
  ViewabilityConfigCallbackPair,
25
25
  } from './Lists/ViewabilityHelper';
26
26
  export type {
27
+ CellRendererProps,
27
28
  RenderItemProps,
28
29
  RenderItemType,
29
30
  Separators,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native/virtualized-lists",
3
- "version": "0.72.1",
3
+ "version": "0.72.3",
4
4
  "description": "Virtualized lists for React Native.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,8 @@
9
9
  },
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "invariant": "^2.2.4"
12
+ "invariant": "^2.2.4",
13
+ "nullthrows": "^1.1.1"
13
14
  },
14
15
  "devDependencies": {
15
16
  "react-test-renderer": "18.2.0"