@shopify/flash-list 2.0.0-alpha.10 → 2.0.0-alpha.12

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.
Files changed (121) hide show
  1. package/README.md +6 -2
  2. package/dist/AnimatedFlashList.d.ts.map +1 -1
  3. package/dist/AnimatedFlashList.js +3 -3
  4. package/dist/AnimatedFlashList.js.map +1 -1
  5. package/dist/FlashList.d.ts +9 -0
  6. package/dist/FlashList.d.ts.map +1 -1
  7. package/dist/FlashList.js +20 -0
  8. package/dist/FlashList.js.map +1 -1
  9. package/dist/FlashListProps.d.ts +13 -6
  10. package/dist/FlashListProps.d.ts.map +1 -1
  11. package/dist/FlashListProps.js.map +1 -1
  12. package/dist/FlashListRef.d.ts +305 -0
  13. package/dist/FlashListRef.d.ts.map +1 -0
  14. package/dist/FlashListRef.js +3 -0
  15. package/dist/FlashListRef.js.map +1 -0
  16. package/dist/__tests__/RecyclerView.test.js +62 -27
  17. package/dist/__tests__/RecyclerView.test.js.map +1 -1
  18. package/dist/__tests__/RenderStackManager.test.d.ts +2 -0
  19. package/dist/__tests__/RenderStackManager.test.d.ts.map +1 -0
  20. package/dist/__tests__/RenderStackManager.test.js +405 -0
  21. package/dist/__tests__/RenderStackManager.test.js.map +1 -0
  22. package/dist/__tests__/useUnmountAwareCallbacks.test.js +1 -1
  23. package/dist/__tests__/useUnmountAwareCallbacks.test.js.map +1 -1
  24. package/dist/benchmark/useFlatListBenchmark.js +8 -7
  25. package/dist/benchmark/useFlatListBenchmark.js.map +1 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/recyclerview/RecyclerView.d.ts +2 -1
  30. package/dist/recyclerview/RecyclerView.d.ts.map +1 -1
  31. package/dist/recyclerview/RecyclerView.js +39 -21
  32. package/dist/recyclerview/RecyclerView.js.map +1 -1
  33. package/dist/recyclerview/RecyclerViewContextProvider.d.ts +6 -5
  34. package/dist/recyclerview/RecyclerViewContextProvider.d.ts.map +1 -1
  35. package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -1
  36. package/dist/recyclerview/RecyclerViewManager.d.ts +14 -7
  37. package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -1
  38. package/dist/recyclerview/RecyclerViewManager.js +71 -102
  39. package/dist/recyclerview/RecyclerViewManager.js.map +1 -1
  40. package/dist/recyclerview/RenderStackManager.d.ts +85 -0
  41. package/dist/recyclerview/RenderStackManager.d.ts.map +1 -0
  42. package/dist/recyclerview/RenderStackManager.js +261 -0
  43. package/dist/recyclerview/RenderStackManager.js.map +1 -0
  44. package/dist/recyclerview/ViewHolder.d.ts.map +1 -1
  45. package/dist/recyclerview/ViewHolder.js +5 -3
  46. package/dist/recyclerview/ViewHolder.js.map +1 -1
  47. package/dist/recyclerview/ViewHolderCollection.d.ts +3 -1
  48. package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -1
  49. package/dist/recyclerview/ViewHolderCollection.js +19 -3
  50. package/dist/recyclerview/ViewHolderCollection.js.map +1 -1
  51. package/dist/recyclerview/components/ScrollAnchor.d.ts +2 -1
  52. package/dist/recyclerview/components/ScrollAnchor.d.ts.map +1 -1
  53. package/dist/recyclerview/components/ScrollAnchor.js +9 -4
  54. package/dist/recyclerview/components/ScrollAnchor.js.map +1 -1
  55. package/dist/recyclerview/components/StickyHeaders.d.ts +1 -1
  56. package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -1
  57. package/dist/recyclerview/components/StickyHeaders.js +39 -32
  58. package/dist/recyclerview/components/StickyHeaders.js.map +1 -1
  59. package/dist/recyclerview/hooks/useBoundDetection.d.ts +1 -2
  60. package/dist/recyclerview/hooks/useBoundDetection.d.ts.map +1 -1
  61. package/dist/recyclerview/hooks/useBoundDetection.js +19 -16
  62. package/dist/recyclerview/hooks/useBoundDetection.js.map +1 -1
  63. package/dist/recyclerview/hooks/useOnLoad.d.ts.map +1 -1
  64. package/dist/recyclerview/hooks/useOnLoad.js +4 -6
  65. package/dist/recyclerview/hooks/useOnLoad.js.map +1 -1
  66. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts +3 -48
  67. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -1
  68. package/dist/recyclerview/hooks/useRecyclerViewController.js +111 -77
  69. package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -1
  70. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts.map +1 -1
  71. package/dist/recyclerview/hooks/useRecyclerViewManager.js +6 -0
  72. package/dist/recyclerview/hooks/useRecyclerViewManager.js.map +1 -1
  73. package/dist/recyclerview/hooks/useSecondaryProps.js +1 -1
  74. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts +10 -3
  75. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts.map +1 -1
  76. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js +33 -4
  77. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js.map +1 -1
  78. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts +6 -0
  79. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts.map +1 -1
  80. package/dist/recyclerview/layout-managers/GridLayoutManager.js +27 -5
  81. package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -1
  82. package/dist/recyclerview/layout-managers/LayoutManager.d.ts +2 -2
  83. package/dist/recyclerview/layout-managers/LayoutManager.js +2 -2
  84. package/dist/tsconfig.tsbuildinfo +1 -1
  85. package/jestSetup.js +30 -11
  86. package/package.json +1 -1
  87. package/src/AnimatedFlashList.ts +3 -2
  88. package/src/FlashList.tsx +24 -0
  89. package/src/FlashListProps.ts +16 -7
  90. package/src/FlashListRef.ts +320 -0
  91. package/src/__tests__/RecyclerView.test.tsx +83 -29
  92. package/src/__tests__/RenderStackManager.test.ts +488 -0
  93. package/src/__tests__/useUnmountAwareCallbacks.test.tsx +12 -12
  94. package/src/benchmark/useFlatListBenchmark.ts +2 -2
  95. package/src/index.ts +1 -0
  96. package/src/recyclerview/RecyclerView.tsx +49 -29
  97. package/src/recyclerview/RecyclerViewContextProvider.ts +12 -6
  98. package/src/recyclerview/RecyclerViewManager.ts +90 -88
  99. package/src/recyclerview/RenderStackManager.ts +265 -0
  100. package/src/recyclerview/ViewHolder.tsx +5 -3
  101. package/src/recyclerview/ViewHolderCollection.tsx +29 -8
  102. package/src/recyclerview/components/ScrollAnchor.tsx +21 -8
  103. package/src/recyclerview/components/StickyHeaders.tsx +62 -44
  104. package/src/recyclerview/hooks/useBoundDetection.ts +25 -18
  105. package/src/recyclerview/hooks/useOnLoad.ts +4 -6
  106. package/src/recyclerview/hooks/useRecyclerViewController.tsx +121 -132
  107. package/src/recyclerview/hooks/useRecyclerViewManager.ts +6 -0
  108. package/src/recyclerview/hooks/useSecondaryProps.tsx +1 -1
  109. package/src/recyclerview/hooks/useUnmountAwareCallbacks.ts +39 -3
  110. package/src/recyclerview/layout-managers/GridLayoutManager.ts +30 -7
  111. package/src/recyclerview/layout-managers/LayoutManager.ts +2 -2
  112. package/dist/__tests__/RecycleKeyManager.test.d.ts +0 -2
  113. package/dist/__tests__/RecycleKeyManager.test.d.ts.map +0 -1
  114. package/dist/__tests__/RecycleKeyManager.test.js +0 -210
  115. package/dist/__tests__/RecycleKeyManager.test.js.map +0 -1
  116. package/dist/recyclerview/RecycleKeyManager.d.ts +0 -82
  117. package/dist/recyclerview/RecycleKeyManager.d.ts.map +0 -1
  118. package/dist/recyclerview/RecycleKeyManager.js +0 -135
  119. package/dist/recyclerview/RecycleKeyManager.js.map +0 -1
  120. package/src/__tests__/RecycleKeyManager.test.ts +0 -254
  121. package/src/recyclerview/RecycleKeyManager.ts +0 -185
@@ -19,6 +19,8 @@ import {
19
19
  NativeSyntheticEvent,
20
20
  } from "react-native";
21
21
 
22
+ import { FlashListRef } from "../FlashListRef";
23
+
22
24
  import { RVDimension } from "./layout-managers/LayoutManager";
23
25
  import {
24
26
  areDimensionsNotEqual,
@@ -55,7 +57,7 @@ import { RenderTimeTracker } from "./helpers/RenderTimeTracker";
55
57
  */
56
58
  const RecyclerViewComponent = <T,>(
57
59
  props: RecyclerViewProps<T>,
58
- ref: React.Ref<any>
60
+ ref: React.Ref<FlashListRef<T>>
59
61
  ) => {
60
62
  // Destructure props and initialize refs
61
63
  const {
@@ -76,8 +78,6 @@ const RecyclerViewComponent = <T,>(
76
78
  ListFooterComponentStyle,
77
79
  ItemSeparatorComponent,
78
80
  renderScrollComponent,
79
- onScroll,
80
- disableRecycling,
81
81
  style,
82
82
  stickyHeaderIndices,
83
83
  maintainVisibleContentPosition,
@@ -116,13 +116,12 @@ const RecyclerViewComponent = <T,>(
116
116
  // Initialize core RecyclerView manager and content offset management
117
117
  const { recyclerViewManager, velocityTracker } =
118
118
  useRecyclerViewManager(props);
119
- const { applyContentOffset, applyInitialScrollIndex } =
119
+ const { applyContentOffset, applyInitialScrollIndex, handlerMethods } =
120
120
  useRecyclerViewController(
121
121
  recyclerViewManager,
122
122
  ref,
123
123
  scrollViewRef,
124
- scrollAnchorRef,
125
- props
124
+ scrollAnchorRef
126
125
  );
127
126
 
128
127
  // Initialize view holder collection ref
@@ -132,11 +131,7 @@ const RecyclerViewComponent = <T,>(
132
131
  useOnListLoad(recyclerViewManager, onLoad);
133
132
 
134
133
  // Hook to detect when scrolling reaches list bounds
135
- const { checkBounds } = useBoundDetection(
136
- recyclerViewManager,
137
- props,
138
- scrollViewRef
139
- );
134
+ const { checkBounds } = useBoundDetection(recyclerViewManager, scrollViewRef);
140
135
 
141
136
  const isHorizontalRTL = I18nManager.isRTL && horizontal;
142
137
 
@@ -182,6 +177,7 @@ const RecyclerViewComponent = <T,>(
182
177
  * Effect to handle layout updates for list items
183
178
  * This ensures proper positioning and recycling of items
184
179
  */
180
+ // eslint-disable-next-line react-hooks/exhaustive-deps
185
181
  useLayoutEffect(() => {
186
182
  if (pendingChildIds.size > 0) {
187
183
  return;
@@ -189,7 +185,7 @@ const RecyclerViewComponent = <T,>(
189
185
  const layoutInfo = Array.from(refHolder, ([index, viewHolderRef]) => {
190
186
  const layout = measureItemLayout(
191
187
  viewHolderRef.current!,
192
- recyclerViewManager.getLayout(index)
188
+ recyclerViewManager.tryGetLayout(index)
193
189
  );
194
190
 
195
191
  // comapre height with stored layout
@@ -272,22 +268,31 @@ const RecyclerViewComponent = <T,>(
272
268
  recyclerViewManager.computeItemViewability();
273
269
 
274
270
  // Call user-provided onScroll handler
275
- onScroll?.(event);
271
+ recyclerViewManager.props.onScroll?.(event);
276
272
  },
277
- [horizontal, isHorizontalRTL, recyclerViewManager]
273
+ [
274
+ checkBounds,
275
+ horizontal,
276
+ isHorizontalRTL,
277
+ recyclerViewManager,
278
+ velocityTracker,
279
+ ]
278
280
  );
279
281
 
280
282
  // Create context for child components
281
- const recyclerViewContext: RecyclerViewContext = useMemo(() => {
283
+ const recyclerViewContext: RecyclerViewContext<T> = useMemo(() => {
282
284
  return {
283
285
  layout: () => {
284
286
  setLayoutTreeId((prev) => prev + 1);
285
287
  },
286
288
  getRef: () => {
287
- return ref;
289
+ if (recyclerViewManager.isDisposed) {
290
+ return null;
291
+ }
292
+ return handlerMethods;
288
293
  },
289
294
  getScrollViewRef: () => {
290
- return scrollViewRef;
295
+ return scrollViewRef.current;
291
296
  },
292
297
  markChildLayoutAsPending: (id: string) => {
293
298
  pendingChildIds.add(id);
@@ -299,7 +304,7 @@ const RecyclerViewComponent = <T,>(
299
304
  }
300
305
  },
301
306
  };
302
- }, [setLayoutTreeId]);
307
+ }, [handlerMethods, pendingChildIds, recyclerViewManager, setLayoutTreeId]);
303
308
 
304
309
  const parentRecyclerViewContext = useRecyclerViewContext();
305
310
  const recyclerViewId = useId();
@@ -334,7 +339,7 @@ const RecyclerViewComponent = <T,>(
334
339
  recyclerViewContext.layout();
335
340
  }
336
341
  },
337
- [recyclerViewManager]
342
+ [recyclerViewContext, recyclerViewManager]
338
343
  );
339
344
 
340
345
  // Get secondary props and components
@@ -374,7 +379,14 @@ const RecyclerViewComponent = <T,>(
374
379
  );
375
380
  }
376
381
  return null;
377
- }, [data, stickyHeaderIndices, renderItem, extraData]);
382
+ }, [
383
+ data,
384
+ stickyHeaderIndices,
385
+ renderItem,
386
+ scrollY,
387
+ recyclerViewManager,
388
+ extraData,
389
+ ]);
378
390
 
379
391
  // Set up scroll event handling with animation support for sticky headers
380
392
  const animatedEvent = useMemo(() => {
@@ -385,21 +397,23 @@ const RecyclerViewComponent = <T,>(
385
397
  );
386
398
  }
387
399
  return onScrollHandler;
388
- }, [onScrollHandler, stickyHeaders]);
400
+ }, [onScrollHandler, scrollY, stickyHeaders]);
401
+
402
+ const shouldMaintainVisibleContentPosition =
403
+ recyclerViewManager.shouldMaintainVisibleContentPosition();
389
404
 
390
405
  const maintainVisibleContentPositionInternal = useMemo(() => {
391
- if (maintainVisibleContentPosition?.disabled || horizontal) {
392
- return undefined;
393
- } else {
406
+ if (shouldMaintainVisibleContentPosition) {
394
407
  return {
395
408
  ...maintainVisibleContentPosition,
396
409
  minIndexForVisible: 0,
397
410
  };
398
411
  }
399
- }, [maintainVisibleContentPosition]);
412
+ return undefined;
413
+ }, [maintainVisibleContentPosition, shouldMaintainVisibleContentPosition]);
400
414
 
401
415
  const shouldRenderFromBottom =
402
- maintainVisibleContentPositionInternal?.startRenderingFromBottom ?? false;
416
+ maintainVisibleContentPosition?.startRenderingFromBottom ?? false;
403
417
 
404
418
  // Calculate minimum height adjustment for bottom rendering
405
419
  const adjustmentMinHeight = recyclerViewManager.hasLayout()
@@ -472,7 +486,10 @@ const RecyclerViewComponent = <T,>(
472
486
  >
473
487
  {/* Scroll anchor for maintaining content position */}
474
488
  {maintainVisibleContentPositionInternal && (
475
- <ScrollAnchor scrollAnchorRef={scrollAnchorRef} />
489
+ <ScrollAnchor
490
+ horizontal={Boolean(horizontal)}
491
+ scrollAnchorRef={scrollAnchorRef}
492
+ />
476
493
  )}
477
494
  {isHorizontalRTL && viewToMeasureBoundedSize}
478
495
  {renderHeader}
@@ -503,7 +520,7 @@ const RecyclerViewComponent = <T,>(
503
520
  applyInitialScrollIndex();
504
521
  checkBounds();
505
522
  recyclerViewManager.computeItemViewability();
506
- recyclerViewManager.disableRecycling = Boolean(disableRecycling);
523
+ recyclerViewManager.disableRecycling(false);
507
524
  }}
508
525
  CellRendererComponent={CellRendererComponent}
509
526
  ItemSeparatorComponent={ItemSeparatorComponent}
@@ -522,9 +539,12 @@ const RecyclerViewComponent = <T,>(
522
539
  );
523
540
  };
524
541
 
542
+ // Set displayName for the inner component
543
+ RecyclerViewComponent.displayName = "FlashList";
544
+
525
545
  // Type definition for the RecyclerView component
526
546
  type RecyclerViewType = <T>(
527
- props: RecyclerViewProps<T> & { ref?: React.Ref<any> }
547
+ props: RecyclerViewProps<T> & { ref?: React.Ref<FlashListRef<T>> }
528
548
  ) => React.JSX.Element;
529
549
 
530
550
  // Create and export the memoized, forwarded ref component
@@ -1,20 +1,26 @@
1
1
  import { createContext, useContext } from "react";
2
2
 
3
+ import { FlashListRef } from "../FlashListRef";
4
+
3
5
  import { CompatScroller } from "./components/CompatScroller";
4
6
 
5
- export interface RecyclerViewContext {
7
+ export interface RecyclerViewContext<T> {
6
8
  layout: () => void;
7
- getRef: () => React.Ref<any>;
8
- getScrollViewRef: () => React.RefObject<CompatScroller | null>;
9
+ getRef: () => FlashListRef<T> | null;
10
+ getScrollViewRef: () => CompatScroller | null;
9
11
  markChildLayoutAsPending: (id: string) => void;
10
12
  unmarkChildLayoutAsPending: (id: string) => void;
11
13
  }
12
14
 
13
15
  const RecyclerViewContextInstance = createContext<
14
- RecyclerViewContext | undefined
16
+ RecyclerViewContext<unknown> | undefined
15
17
  >(undefined);
16
18
 
17
19
  export const RecyclerViewContextProvider = RecyclerViewContextInstance.Provider;
18
- export function useRecyclerViewContext() {
19
- return useContext(RecyclerViewContextInstance);
20
+ export function useRecyclerViewContext<T>():
21
+ | RecyclerViewContext<T>
22
+ | undefined {
23
+ return useContext(RecyclerViewContextInstance) as
24
+ | RecyclerViewContext<T>
25
+ | undefined;
20
26
  }
@@ -9,30 +9,27 @@ import {
9
9
  } from "./layout-managers/LayoutManager";
10
10
  import { RVLinearLayoutManagerImpl } from "./layout-managers/LinearLayoutManager";
11
11
  import { RVMasonryLayoutManagerImpl } from "./layout-managers/MasonryLayoutManager";
12
- import { RecycleKeyManagerImpl, RecycleKeyManager } from "./RecycleKeyManager";
13
12
  import { RecyclerViewProps } from "./RecyclerViewProps";
14
13
  import {
15
14
  RVEngagedIndicesTracker,
16
15
  RVEngagedIndicesTrackerImpl,
17
16
  Velocity,
18
17
  } from "./helpers/EngagedIndicesTracker";
19
-
20
- // Abstracts layout manager, key manager and viewability manager and generates render stack (progressively on load)
18
+ import { RenderStackManager } from "./RenderStackManager";
19
+ // Abstracts layout manager, render stack manager and viewability manager and generates render stack (progressively on load)
21
20
  export class RecyclerViewManager<T> {
22
21
  private initialDrawBatchSize = 1;
23
22
  private engagedIndicesTracker: RVEngagedIndicesTracker;
24
- private recycleKeyManager: RecycleKeyManager;
23
+ private renderStackManager: RenderStackManager;
25
24
  private layoutManager?: RVLayoutManager;
26
25
  // Map of index to key
27
- private renderStack: Map<number, string> = new Map();
28
26
  private isFirstLayoutComplete = false;
29
27
  private hasRenderedProgressively = false;
30
- private props: RecyclerViewProps<T>;
28
+ private propsRef: RecyclerViewProps<T>;
31
29
  private itemViewabilityManager: ViewabilityManager<T>;
32
- private allocatedKeyTracker: Set<string> = new Set();
33
30
  private _isDisposed = false;
31
+ private _isLayoutManagerDirty = false;
34
32
 
35
- public disableRecycling = false;
36
33
  public firstItemOffset = 0;
37
34
  public ignoreScrollEvents = false;
38
35
 
@@ -45,74 +42,46 @@ export class RecyclerViewManager<T> {
45
42
  }
46
43
 
47
44
  constructor(props: RecyclerViewProps<T>) {
48
- this.props = props;
45
+ this.getStableId = this.getStableId.bind(this);
46
+ this.getItemType = this.getItemType.bind(this);
47
+ this.propsRef = props;
49
48
  this.engagedIndicesTracker = new RVEngagedIndicesTrackerImpl();
50
- this.recycleKeyManager = new RecycleKeyManagerImpl();
49
+ this.renderStackManager = new RenderStackManager(
50
+ props.maxItemsInRecyclePool
51
+ );
51
52
  this.itemViewabilityManager = new ViewabilityManager<T>(this as any);
52
53
  }
53
54
 
54
55
  // updates render stack based on the engaged indices which are sorted. Recycles unused keys.
55
- // TODO: Write comprehensive tests for this function
56
56
  private updateRenderStack = (engagedIndices: ConsecutiveNumbers): void => {
57
- // console.log("updateRenderStack", engagedIndices);
58
-
59
- this.allocatedKeyTracker.clear();
60
- const newRenderStack = new Map<number, string>();
61
- for (const [index, key] of this.renderStack) {
62
- if (!engagedIndices.includes(index)) {
63
- this.recycleKeyManager.recycleKey(key);
64
- }
65
- }
66
- if (this.disableRecycling) {
67
- this.recycleKeyManager.clearPool();
68
- }
69
- for (const index of engagedIndices) {
70
- const currentKey = this.renderStack.get(index);
71
-
72
- if (
73
- currentKey &&
74
- !this.disableRecycling &&
75
- !this.allocatedKeyTracker.has(currentKey)
76
- ) {
77
- this.recycleKeyManager.recycleKey(currentKey);
78
- }
79
-
80
- const newKey = this.recycleKeyManager.getKey(
81
- this.getItemType(index),
82
- this.getStableId(index),
83
- currentKey
84
- );
85
- this.allocatedKeyTracker.add(newKey);
86
- newRenderStack.set(index, newKey);
87
- }
88
- // DANGER
89
- for (const [index, key] of this.renderStack) {
90
- if (
91
- this.recycleKeyManager.hasKeyInPool(key) &&
92
- !newRenderStack.has(index) &&
93
- index < (this.props.data?.length ?? 0)
94
- ) {
95
- this.allocatedKeyTracker.add(key);
96
- newRenderStack.set(index, key);
97
- }
98
- }
99
-
100
- this.renderStack = newRenderStack;
57
+ this.renderStackManager.sync(
58
+ this.getStableId,
59
+ this.getItemType,
60
+ engagedIndices,
61
+ this.getDataLength()
62
+ );
101
63
  };
102
64
 
65
+ get props() {
66
+ return this.propsRef;
67
+ }
68
+
103
69
  setOffsetProjectionEnabled(value: boolean) {
104
70
  this.engagedIndicesTracker.enableOffsetProjection = value;
105
71
  }
106
72
 
107
73
  updateProps(props: RecyclerViewProps<T>) {
108
- this.props = props;
74
+ this.propsRef = props;
109
75
  this.engagedIndicesTracker.drawDistance =
110
76
  props.drawDistance ?? this.engagedIndicesTracker.drawDistance;
111
- if (this.props.drawDistance === 0) {
77
+ if (this.propsRef.drawDistance === 0) {
112
78
  this.initialDrawBatchSize = 1;
113
79
  } else {
114
80
  this.initialDrawBatchSize = (props.numColumns ?? 1) * 2;
115
81
  }
82
+ this.initialDrawBatchSize =
83
+ this.propsRef.overrideProps?.initialDrawBatchSize ??
84
+ this.initialDrawBatchSize;
116
85
  }
117
86
 
118
87
  /**
@@ -124,7 +93,7 @@ export class RecyclerViewManager<T> {
124
93
  offset: number,
125
94
  velocity?: Velocity
126
95
  ): ConsecutiveNumbers | undefined {
127
- if (this.layoutManager && !this.isDisposed) {
96
+ if (this.layoutManager && !this._isDisposed) {
128
97
  const engagedIndices = this.engagedIndicesTracker.updateScrollOffset(
129
98
  offset - this.firstItemOffset,
130
99
  velocity,
@@ -147,6 +116,10 @@ export class RecyclerViewManager<T> {
147
116
  return this.isFirstLayoutComplete;
148
117
  }
149
118
 
119
+ disableRecycling(disable: boolean) {
120
+ this.renderStackManager.disableRecycling = disable;
121
+ }
122
+
150
123
  getLayout(index: number) {
151
124
  if (!this.layoutManager) {
152
125
  throw new Error(
@@ -156,6 +129,17 @@ export class RecyclerViewManager<T> {
156
129
  return this.layoutManager.getLayout(index);
157
130
  }
158
131
 
132
+ tryGetLayout(index: number) {
133
+ if (
134
+ this.layoutManager &&
135
+ index >= 0 &&
136
+ index < this.layoutManager.getLayoutCount()
137
+ ) {
138
+ return this.layoutManager.getLayout(index);
139
+ }
140
+ return undefined;
141
+ }
142
+
159
143
  // Doesn't include header / foot etc
160
144
  getChildContainerDimensions() {
161
145
  if (!this.layoutManager) {
@@ -167,7 +151,7 @@ export class RecyclerViewManager<T> {
167
151
  }
168
152
 
169
153
  getRenderStack() {
170
- return this.renderStack;
154
+ return this.renderStackManager.getRenderStack();
171
155
  }
172
156
 
173
157
  getWindowSize() {
@@ -187,10 +171,10 @@ export class RecyclerViewManager<T> {
187
171
  getMaxScrollOffset() {
188
172
  return Math.max(
189
173
  0,
190
- (this.props.horizontal
174
+ (this.propsRef.horizontal
191
175
  ? this.getChildContainerDimensions().width
192
176
  : this.getChildContainerDimensions().height) -
193
- (this.props.horizontal
177
+ (this.propsRef.horizontal
194
178
  ? this.getWindowSize().width
195
179
  : this.getWindowSize().height) +
196
180
  this.firstItemOffset
@@ -216,28 +200,33 @@ export class RecyclerViewManager<T> {
216
200
  if (
217
201
  this.layoutManager &&
218
202
  Boolean(this.layoutManager?.isHorizontal()) !==
219
- Boolean(this.props.horizontal)
203
+ Boolean(this.propsRef.horizontal)
220
204
  ) {
221
205
  throw new Error(
222
206
  "Horizontal prop cannot be toggled, you can use a key on FlashList to recreate it."
223
207
  );
224
208
  }
209
+ if (this._isLayoutManagerDirty) {
210
+ this.layoutManager = undefined;
211
+ this._isLayoutManagerDirty = false;
212
+ }
225
213
  if (!(this.layoutManager instanceof LayoutManagerClass)) {
226
214
  // console.log("-----> new LayoutManagerClass");
227
215
 
228
216
  this.layoutManager = new LayoutManagerClass(
229
217
  {
230
218
  windowSize,
231
- maxColumns: this.props.numColumns ?? 1,
232
- horizontal: Boolean(this.props.horizontal),
233
- optimizeItemArrangement: this.props.optimizeItemArrangement ?? true,
219
+ maxColumns: this.propsRef.numColumns ?? 1,
220
+ horizontal: Boolean(this.propsRef.horizontal),
221
+ optimizeItemArrangement:
222
+ this.propsRef.optimizeItemArrangement ?? true,
234
223
  overrideItemLayout: (index, layout) => {
235
- this.props?.overrideItemLayout?.(
224
+ this.propsRef?.overrideItemLayout?.(
236
225
  layout,
237
- this.props.data![index],
226
+ this.propsRef.data![index],
238
227
  index,
239
- this.props.numColumns ?? 1,
240
- this.props.extraData
228
+ this.propsRef.numColumns ?? 1,
229
+ this.propsRef.extraData
241
230
  );
242
231
  },
243
232
  },
@@ -246,9 +235,9 @@ export class RecyclerViewManager<T> {
246
235
  } else {
247
236
  this.layoutManager.updateLayoutParams({
248
237
  windowSize,
249
- maxColumns: this.props.numColumns ?? 1,
250
- horizontal: Boolean(this.props.horizontal),
251
- optimizeItemArrangement: this.props.optimizeItemArrangement ?? true,
238
+ maxColumns: this.propsRef.numColumns ?? 1,
239
+ horizontal: Boolean(this.propsRef.horizontal),
240
+ optimizeItemArrangement: this.propsRef.optimizeItemArrangement ?? true,
252
241
  });
253
242
  }
254
243
  }
@@ -257,7 +246,7 @@ export class RecyclerViewManager<T> {
257
246
  return this.layoutManager !== undefined;
258
247
  }
259
248
 
260
- getVisibleIndices() {
249
+ computeVisibleIndices() {
261
250
  if (!this.layoutManager) {
262
251
  throw new Error(
263
252
  "LayoutManager is not initialized, visible indices are not unavailable"
@@ -295,9 +284,9 @@ export class RecyclerViewManager<T> {
295
284
  // Using higher buffer for masonry to avoid missing items
296
285
  this.itemViewabilityManager.shouldListenToVisibleIndices &&
297
286
  this.itemViewabilityManager.updateViewableItems(
298
- this.props.masonry
287
+ this.propsRef.masonry
299
288
  ? this.engagedIndicesTracker.getEngagedIndices().toArray()
300
- : this.getVisibleIndices().toArray()
289
+ : this.computeVisibleIndices().toArray()
301
290
  );
302
291
  }
303
292
 
@@ -311,7 +300,7 @@ export class RecyclerViewManager<T> {
311
300
 
312
301
  processDataUpdate() {
313
302
  if (this.hasLayout()) {
314
- this.modifyChildrenLayout([], this.props.data?.length ?? 0);
303
+ this.modifyChildrenLayout([], this.propsRef.data?.length ?? 0);
315
304
  if (!this.recomputeEngagedIndices()) {
316
305
  // recomputeEngagedIndices will update the render stack if there are any changes in the engaged indices.
317
306
  // It's important to update render stack so that elements are assgined right keys incase items were deleted.
@@ -329,30 +318,42 @@ export class RecyclerViewManager<T> {
329
318
  this.itemViewabilityManager.dispose();
330
319
  }
331
320
 
321
+ markLayoutManagerDirty() {
322
+ this._isLayoutManagerDirty = true;
323
+ }
324
+
332
325
  getInitialScrollIndex() {
333
326
  return (
334
- this.props.initialScrollIndex ??
335
- (this.props.maintainVisibleContentPosition?.startRenderingFromBottom
327
+ this.propsRef.initialScrollIndex ??
328
+ (this.propsRef.maintainVisibleContentPosition?.startRenderingFromBottom
336
329
  ? this.getDataLength() - 1
337
330
  : undefined)
338
331
  );
339
332
  }
340
333
 
334
+ shouldMaintainVisibleContentPosition() {
335
+ // Return true if maintainVisibleContentPosition is enabled and not horizontal
336
+ return (
337
+ !this.propsRef.maintainVisibleContentPosition?.disabled &&
338
+ !this.propsRef.horizontal
339
+ );
340
+ }
341
+
341
342
  getDataLength() {
342
- return this.props.data?.length ?? 0;
343
+ return this.propsRef.data?.length ?? 0;
343
344
  }
344
345
 
345
346
  private getLayoutManagerClass() {
346
347
  // throw errors for incompatible props
347
- if (this.props.masonry && this.props.horizontal) {
348
+ if (this.propsRef.masonry && this.propsRef.horizontal) {
348
349
  throw new Error("Masonry and horizontal props are incompatible");
349
350
  }
350
- if ((this.props.numColumns ?? 1) > 1 && this.props.horizontal) {
351
+ if ((this.propsRef.numColumns ?? 1) > 1 && this.propsRef.horizontal) {
351
352
  throw new Error("numColumns and horizontal props are incompatible");
352
353
  }
353
- return this.props.masonry
354
+ return this.propsRef.masonry
354
355
  ? RVMasonryLayoutManagerImpl
355
- : (this.props.numColumns ?? 1) > 1 && !this.props.horizontal
356
+ : (this.propsRef.numColumns ?? 1) > 1 && !this.propsRef.horizontal
356
357
  ? RVGridLayoutManagerImpl
357
358
  : RVLinearLayoutManagerImpl;
358
359
  }
@@ -366,7 +367,7 @@ export class RecyclerViewManager<T> {
366
367
  const initialItemLayout = this.layoutManager?.getLayout(
367
368
  initialScrollIndex ?? 0
368
369
  );
369
- const initialItemOffset = this.props.horizontal
370
+ const initialItemOffset = this.propsRef.horizontal
370
371
  ? initialItemLayout?.x
371
372
  : initialItemLayout?.y;
372
373
 
@@ -391,7 +392,7 @@ export class RecyclerViewManager<T> {
391
392
  const layoutManager = this.layoutManager;
392
393
  if (layoutManager) {
393
394
  this.applyInitialScrollAdjustment();
394
- const visibleIndices = this.getVisibleIndices();
395
+ const visibleIndices = this.computeVisibleIndices();
395
396
  // console.log("---------> visibleIndices", visibleIndices);
396
397
  this.hasRenderedProgressively = visibleIndices.every(
397
398
  (index) =>
@@ -412,7 +413,7 @@ export class RecyclerViewManager<T> {
412
413
  0,
413
414
  Math.min(
414
415
  visibleIndices.length,
415
- this.renderStack.size + this.initialDrawBatchSize
416
+ this.getRenderStack().size + this.initialDrawBatchSize
416
417
  )
417
418
  )
418
419
  );
@@ -421,13 +422,14 @@ export class RecyclerViewManager<T> {
421
422
 
422
423
  private getItemType(index: number): string {
423
424
  return (
424
- this.props.getItemType?.(this.props.data![index], index) ?? "default"
425
+ this.propsRef.getItemType?.(this.propsRef.data![index], index) ??
426
+ "default"
425
427
  ).toString();
426
428
  }
427
429
 
428
430
  private getStableId(index: number): string {
429
431
  return (
430
- this.props.keyExtractor?.(this.props.data![index], index) ??
432
+ this.propsRef.keyExtractor?.(this.propsRef.data![index], index) ??
431
433
  index.toString()
432
434
  );
433
435
  }