@react-native/virtualized-lists 0.72.2 → 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
 
@@ -712,29 +748,31 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
712
748
  const end = getItemCount(data) - 1;
713
749
  let prevCellKey;
714
750
  last = Math.min(end, last);
751
+
715
752
  for (let ii = first; ii <= last; ii++) {
716
753
  const item = getItem(data, ii);
717
754
  const key = this._keyExtractor(item, ii, this.props);
755
+
718
756
  this._indicesToKeys.set(ii, key);
719
757
  if (stickyIndicesFromProps.has(ii + stickyOffset)) {
720
758
  stickyHeaderIndices.push(cells.length);
721
759
  }
760
+
761
+ const shouldListenForLayout =
762
+ getItemLayout == null || debug || this._fillRateHelper.enabled();
763
+
722
764
  cells.push(
723
765
  <CellRenderer
724
766
  CellRendererComponent={CellRendererComponent}
725
767
  ItemSeparatorComponent={ii < end ? ItemSeparatorComponent : undefined}
726
768
  ListItemComponent={ListItemComponent}
727
769
  cellKey={key}
728
- debug={debug}
729
- fillRateHelper={this._fillRateHelper}
730
- getItemLayout={getItemLayout}
731
770
  horizontal={horizontal}
732
771
  index={ii}
733
772
  inversionStyle={inversionStyle}
734
773
  item={item}
735
774
  key={key}
736
775
  prevCellKey={prevCellKey}
737
- onCellLayout={this._onCellLayout}
738
776
  onUpdateSeparators={this._onUpdateSeparators}
739
777
  onCellFocusCapture={e => this._onCellFocusCapture(key)}
740
778
  onUnmount={this._onCellUnmount}
@@ -742,6 +780,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
742
780
  this._cellRefs[key] = ref;
743
781
  }}
744
782
  renderItem={renderItem}
783
+ {...(shouldListenForLayout && {
784
+ onCellLayout: this._onCellLayout,
785
+ })}
745
786
  />,
746
787
  );
747
788
  prevCellKey = key;
@@ -807,16 +848,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
807
848
  }
808
849
 
809
850
  render(): React.Node {
810
- if (__DEV__) {
811
- // $FlowFixMe[underconstrained-implicit-instantiation]
812
- const flatStyles = StyleSheet.flatten(this.props.contentContainerStyle);
813
- if (flatStyles != null && flatStyles.flexWrap === 'wrap') {
814
- console.warn(
815
- '`flexWrap: `wrap`` is not supported with the `VirtualizedList` components.' +
816
- 'Consider using `numColumns` with `FlatList` instead.',
817
- );
818
- }
819
- }
851
+ this._checkProps(this.props);
820
852
  const {ListEmptyComponent, ListFooterComponent, ListHeaderComponent} =
821
853
  this.props;
822
854
  const {data, horizontal} = this.props;
@@ -1231,6 +1263,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
1231
1263
  }
1232
1264
 
1233
1265
  _onCellUnmount = (cellKey: string) => {
1266
+ delete this._cellRefs[cellKey];
1234
1267
  const curr = this._frames[cellKey];
1235
1268
  if (curr) {
1236
1269
  this._frames[cellKey] = {...curr, inLayout: false};
@@ -1507,10 +1540,17 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
1507
1540
  !this._hasTriggeredInitialScrollToIndex
1508
1541
  ) {
1509
1542
  if (this.props.contentOffset == null) {
1510
- this.scrollToIndex({
1511
- animated: false,
1512
- index: this.props.initialScrollIndex,
1513
- });
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
+ }
1514
1554
  }
1515
1555
  this._hasTriggeredInitialScrollToIndex = true;
1516
1556
  }
@@ -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.2",
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"