virtual-scroller 1.11.1 → 1.11.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +9 -9
  3. package/bundle/virtual-scroller-dom.js +1 -1
  4. package/bundle/virtual-scroller-dom.js.map +1 -1
  5. package/bundle/virtual-scroller-react.js +1 -1
  6. package/bundle/virtual-scroller-react.js.map +1 -1
  7. package/bundle/virtual-scroller.js +1 -1
  8. package/bundle/virtual-scroller.js.map +1 -1
  9. package/commonjs/DOM/ItemsContainer.js +22 -9
  10. package/commonjs/DOM/ItemsContainer.js.map +1 -1
  11. package/commonjs/DOM/VirtualScroller.js +13 -1
  12. package/commonjs/DOM/VirtualScroller.js.map +1 -1
  13. package/commonjs/ItemHeights.js +5 -5
  14. package/commonjs/ItemHeights.js.map +1 -1
  15. package/commonjs/VirtualScroller.js +21 -4
  16. package/commonjs/VirtualScroller.js.map +1 -1
  17. package/commonjs/VirtualScroller.layout.js +22 -22
  18. package/commonjs/VirtualScroller.layout.js.map +1 -1
  19. package/commonjs/VirtualScroller.state.js +1 -1
  20. package/commonjs/VirtualScroller.state.js.map +1 -1
  21. package/commonjs/react/VirtualScroller.js +53 -20
  22. package/commonjs/react/VirtualScroller.js.map +1 -1
  23. package/commonjs/react/useHandleItemIndexesChange.js +53 -0
  24. package/commonjs/react/useHandleItemIndexesChange.js.map +1 -0
  25. package/commonjs/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +21 -40
  26. package/commonjs/react/useHandleItemsPropertyChange.js.map +1 -0
  27. package/commonjs/react/{useOnItemHeightChange.js → useOnItemHeightDidChange.js} +9 -9
  28. package/commonjs/react/useOnItemHeightDidChange.js.map +1 -0
  29. package/commonjs/react/useSetItemState.js +2 -2
  30. package/commonjs/react/useSetItemState.js.map +1 -1
  31. package/commonjs/react/useState.js +47 -69
  32. package/commonjs/react/useState.js.map +1 -1
  33. package/commonjs/react/useStyle.js +4 -4
  34. package/commonjs/react/useStyle.js.map +1 -1
  35. package/dom/index.d.ts +1 -1
  36. package/index.d.ts +1 -1
  37. package/modules/DOM/ItemsContainer.js +22 -9
  38. package/modules/DOM/ItemsContainer.js.map +1 -1
  39. package/modules/DOM/VirtualScroller.js +13 -1
  40. package/modules/DOM/VirtualScroller.js.map +1 -1
  41. package/modules/ItemHeights.js +5 -5
  42. package/modules/ItemHeights.js.map +1 -1
  43. package/modules/VirtualScroller.js +15 -4
  44. package/modules/VirtualScroller.js.map +1 -1
  45. package/modules/VirtualScroller.layout.js +22 -22
  46. package/modules/VirtualScroller.layout.js.map +1 -1
  47. package/modules/VirtualScroller.state.js +1 -1
  48. package/modules/VirtualScroller.state.js.map +1 -1
  49. package/modules/react/VirtualScroller.js +52 -21
  50. package/modules/react/VirtualScroller.js.map +1 -1
  51. package/modules/react/useHandleItemIndexesChange.js +45 -0
  52. package/modules/react/useHandleItemIndexesChange.js.map +1 -0
  53. package/modules/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +20 -39
  54. package/modules/react/useHandleItemsPropertyChange.js.map +1 -0
  55. package/modules/react/{useOnItemHeightChange.js → useOnItemHeightDidChange.js} +8 -8
  56. package/modules/react/useOnItemHeightDidChange.js.map +1 -0
  57. package/modules/react/useSetItemState.js +2 -2
  58. package/modules/react/useSetItemState.js.map +1 -1
  59. package/modules/react/useState.js +48 -70
  60. package/modules/react/useState.js.map +1 -1
  61. package/modules/react/useStyle.js +4 -4
  62. package/modules/react/useStyle.js.map +1 -1
  63. package/package.json +1 -1
  64. package/source/DOM/ItemsContainer.js +13 -3
  65. package/source/DOM/VirtualScroller.js +11 -1
  66. package/source/ItemHeights.js +5 -5
  67. package/source/VirtualScroller.js +12 -3
  68. package/source/VirtualScroller.layout.js +22 -22
  69. package/source/VirtualScroller.state.js +1 -1
  70. package/source/react/VirtualScroller.js +50 -17
  71. package/source/react/useHandleItemIndexesChange.js +49 -0
  72. package/source/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +20 -38
  73. package/source/react/{useOnItemHeightChange.js → useOnItemHeightDidChange.js} +7 -7
  74. package/source/react/useSetItemState.js +2 -2
  75. package/source/react/useState.js +57 -72
  76. package/source/react/useStyle.js +2 -2
  77. package/commonjs/react/useHandleItemsChange.js.map +0 -1
  78. package/commonjs/react/useOnItemHeightChange.js.map +0 -1
  79. package/modules/react/useHandleItemsChange.js.map +0 -1
  80. package/modules/react/useOnItemHeightChange.js.map +0 -1
@@ -99,7 +99,7 @@ export default function() {
99
99
  // or an "Expand YouTube video" button, which would result
100
100
  // in the actual height of the list item being different
101
101
  // from what has been initially measured in `this.itemHeights[i]`,
102
- // if the developer didn't call `.setItemState(i, newState)` and `.onItemHeightChange(i)`.
102
+ // if the developer didn't call `.setItemState(i, newState)` and `.onItemHeightDidChange(i)`.
103
103
  if (!validateWillBeHiddenItemHeightsAreAccurate.call(this, firstShownItemIndex, lastShownItemIndex)) {
104
104
  log('~ Because some of the will-be-hidden item heights (listed above) have changed since they\'ve last been measured, redo layout. ~')
105
105
  // Redo layout, now with the correct item heights.
@@ -172,9 +172,9 @@ export default function() {
172
172
  // Instead of using a `this.previouslyCalculatedLayout` instance variable,
173
173
  // this code could use `this.getState()` because it reflects what's currently on screen,
174
174
  // but there's a single edge case when it could go out of sync —
175
- // updating item heights externally via `.onItemHeightChange(i)`.
175
+ // updating item heights externally via `.onItemHeightDidChange(i)`.
176
176
  //
177
- // If, for example, an item height was updated externally via `.onItemHeightChange(i)`
177
+ // If, for example, an item height was updated externally via `.onItemHeightDidChange(i)`
178
178
  // then `this.getState().itemHeights` would get updated immediately but
179
179
  // `this.getState().beforeItemsHeight` or `this.getState().afterItemsHeight`
180
180
  // would still correspond to the previous item height, so those would be "stale".
@@ -269,7 +269,7 @@ export default function() {
269
269
  * or an "Expand YouTube video" button, which would result
270
270
  * in the actual height of the list item being different
271
271
  * from what has been initially measured in `this.itemHeights[i]`,
272
- * if the developer didn't call `.setItemState(i, newState)` and `.onItemHeightChange(i)`.
272
+ * if the developer didn't call `.setItemState(i, newState)` and `.onItemHeightDidChange(i)`.
273
273
  */
274
274
  function validateWillBeHiddenItemHeightsAreAccurate(firstShownItemIndex, lastShownItemIndex) {
275
275
  let isValid = true
@@ -281,26 +281,26 @@ export default function() {
281
281
  // The item will be hidden. Re-measure its height.
282
282
  // The rationale is that there could be a situation when an item's
283
283
  // height has changed, and the developer has properly added an
284
- // `.onItemHeightChange(i)` call to notify `VirtualScroller`
284
+ // `.onItemHeightDidChange(i)` call to notify `VirtualScroller`
285
285
  // about that change, but at the same time that wouldn't work.
286
286
  // For example, suppose there's a list of several items on a page,
287
287
  // and those items are in "minimized" state (having height 100px).
288
288
  // Then, a user clicks an "Expand all items" button, and all items
289
289
  // in the list are expanded (expanded item height is gonna be 700px).
290
- // `VirtualScroller` demands that `.onItemHeightChange(i)` is called
290
+ // `VirtualScroller` demands that `.onItemHeightDidChange(i)` is called
291
291
  // in such cases, and the developer has properly added the code to do that.
292
292
  // So, if there were 10 "minimized" items visible on a page, then there
293
- // will be 10 individual `.onItemHeightChange(i)` calls. No issues so far.
294
- // But, as the first `.onItemHeightChange(i)` call executes, it immediately
293
+ // will be 10 individual `.onItemHeightDidChange(i)` calls. No issues so far.
294
+ // But, as the first `.onItemHeightDidChange(i)` call executes, it immediately
295
295
  // ("synchronously") triggers a re-layout, and that re-layout finds out
296
296
  // that now, because the first item is big, it occupies most of the screen
297
297
  // space, and only the first 3 items are visible on screen instead of 10,
298
298
  // and so it leaves the first 3 items mounted and unmounts the rest 7.
299
299
  // Then, after `VirtualScroller` has rerendered, the code returns to
300
- // where it was executing, and calls `.onItemHeightChange(i)` for the
300
+ // where it was executing, and calls `.onItemHeightDidChange(i)` for the
301
301
  // second item. It also triggers an immediate re-layout that finds out
302
302
  // that only the first 2 items are visible on screen, and it unmounts
303
- // the third one too. After that, it calls `.onItemHeightChange(i)`
303
+ // the third one too. After that, it calls `.onItemHeightDidChange(i)`
304
304
  // for the third item, but that item is no longer rendered, so its height
305
305
  // can't be measured, and the same's for all the rest of the original 10 items.
306
306
  // So, even though the developer has written their code properly, the
@@ -318,7 +318,7 @@ export default function() {
318
318
  updatePreviouslyCalculatedLayoutOnItemHeightChange.call(this, i, previouslyMeasuredItemHeight, actualItemHeight)
319
319
  }
320
320
  isValid = false
321
- warn('Item index', i, 'is no longer visible and will be unmounted. Its height has changed from', previouslyMeasuredItemHeight, 'to', actualItemHeight, 'since it was last measured. This is not necessarily a bug, and could happen, for example, on screen width change, or when there\'re several `onItemHeightChange(i)` calls issued at the same time, and the first one triggers a re-layout before the rest of them have had a chance to be executed.')
321
+ warn('Item index', i, 'is no longer visible and will be unmounted. Its height has changed from', previouslyMeasuredItemHeight, 'to', actualItemHeight, 'since it was last measured. This is not necessarily a bug, and could happen, for example, on screen width change, or when there\'re several `onItemHeightDidChange(i)` calls issued at the same time, and the first one triggers a re-layout before the rest of them have had a chance to be executed.')
322
322
  }
323
323
  }
324
324
  i++
@@ -370,9 +370,9 @@ export default function() {
370
370
  return listTopOffset
371
371
  }
372
372
 
373
- this._onItemHeightChange = (i) => {
373
+ this._onItemHeightDidChange = (i) => {
374
374
  log('~ Re-measure item height ~')
375
- log('Item', i)
375
+ log('Item index', i)
376
376
 
377
377
  const {
378
378
  itemHeights,
@@ -383,36 +383,36 @@ export default function() {
383
383
  // Check if the item is still rendered.
384
384
  if (!(i >= firstShownItemIndex && i <= lastShownItemIndex)) {
385
385
  // There could be valid cases when an item is no longer rendered
386
- // by the time `.onItemHeightChange(i)` gets called.
386
+ // by the time `.onItemHeightDidChange(i)` gets called.
387
387
  // For example, suppose there's a list of several items on a page,
388
388
  // and those items are in "minimized" state (having height 100px).
389
389
  // Then, a user clicks an "Expand all items" button, and all items
390
390
  // in the list are expanded (expanded item height is gonna be 700px).
391
- // `VirtualScroller` demands that `.onItemHeightChange(i)` is called
391
+ // `VirtualScroller` demands that `.onItemHeightDidChange(i)` is called
392
392
  // in such cases, and the developer has properly added the code to do that.
393
393
  // So, if there were 10 "minimized" items visible on a page, then there
394
- // will be 10 individual `.onItemHeightChange(i)` calls. No issues so far.
395
- // But, as the first `.onItemHeightChange(i)` call executes, it immediately
394
+ // will be 10 individual `.onItemHeightDidChange(i)` calls. No issues so far.
395
+ // But, as the first `.onItemHeightDidChange(i)` call executes, it immediately
396
396
  // ("synchronously") triggers a re-layout, and that re-layout finds out
397
397
  // that now, because the first item is big, it occupies most of the screen
398
398
  // space, and only the first 3 items are visible on screen instead of 10,
399
399
  // and so it leaves the first 3 items mounted and unmounts the rest 7.
400
400
  // Then, after `VirtualScroller` has rerendered, the code returns to
401
- // where it was executing, and calls `.onItemHeightChange(i)` for the
401
+ // where it was executing, and calls `.onItemHeightDidChange(i)` for the
402
402
  // second item. It also triggers an immediate re-layout that finds out
403
403
  // that only the first 2 items are visible on screen, and it unmounts
404
- // the third one too. After that, it calls `.onItemHeightChange(i)`
404
+ // the third one too. After that, it calls `.onItemHeightDidChange(i)`
405
405
  // for the third item, but that item is no longer rendered, so its height
406
406
  // can't be measured, and the same's for all the rest of the original 10 items.
407
407
  // So, even though the developer has written their code properly, there're
408
408
  // still situations when the item could be no longer rendered by the time
409
- // `.onItemHeightChange(i)` gets called.
410
- return warn('The item is no longer rendered. This is not necessarily a bug, and could happen, for example, when there\'re several `onItemHeightChange(i)` calls issued at the same time.')
409
+ // `.onItemHeightDidChange(i)` gets called.
410
+ return warn('The item is no longer rendered. This is not necessarily a bug, and could happen, for example, when when a developer calls `onItemHeightDidChange(i)` while looping through a batch of items.')
411
411
  }
412
412
 
413
413
  const previousHeight = itemHeights[i]
414
414
  if (previousHeight === undefined) {
415
- return reportError(`"onItemHeightChange()" has been called for item ${i}, but that item hasn't been rendered before.`)
415
+ return reportError(`"onItemHeightDidChange()" has been called for item ${i}, but that item hasn't been rendered before.`)
416
416
  }
417
417
 
418
418
  const newHeight = remeasureItemHeight.call(this, i)
@@ -40,7 +40,7 @@ export default function createStateHelpers({
40
40
  this._setItemState = (i, newItemState) => {
41
41
  if (isDebug()) {
42
42
  log('~ Item state changed ~')
43
- log('Item', i)
43
+ log('Item index', i)
44
44
  // Uses `JSON.stringify()` here instead of just outputting the JSON objects as is
45
45
  // because outputting JSON objects as is would show different results later when
46
46
  // the developer inspects those in the web browser console if those state objects
@@ -7,14 +7,37 @@ import useVirtualScrollerStartStop from './useVirtualScrollerStartStop.js'
7
7
  import useInstanceMethods from './useInstanceMethods.js'
8
8
  import useItemKeys from './useItemKeys.js'
9
9
  import useSetItemState from './useSetItemState.js'
10
- import useOnItemHeightChange from './useOnItemHeightChange.js'
11
- import useHandleItemsChange from './useHandleItemsChange.js'
10
+ import useOnItemHeightDidChange from './useOnItemHeightDidChange.js'
11
+ import useHandleItemsPropertyChange from './useHandleItemsPropertyChange.js'
12
+ import useHandleItemIndexesChange from './useHandleItemIndexesChange.js'
12
13
  import useClassName from './useClassName.js'
13
14
  import useStyle from './useStyle.js'
14
15
 
16
+ // When `items` property changes, `useHandleItemsPropertyChange()` hook detects that
17
+ // and calls `VirtualScroller.setItems()` which in turn calls the `updateState()` function.
18
+ // At this point, an insignificant optimization could be applied:
19
+ // the component could avoid re-rendering the second time.
20
+ // Instead, the state update could be applied "immediately" if it originated
21
+ // from `.setItems()` function call, eliminating the unneeded second re-render.
22
+ //
23
+ // I could see how this minor optimization could get brittle when modifiying the code,
24
+ // so I put it under a feature flag so that it could potentially be turned off
25
+ // in case of any potential weird issues in some future.
26
+ //
27
+ // Another reason for using this feature is:
28
+ //
29
+ // Since `useHandleItemsPropertyChange()` runs at render time
30
+ // and not after the render has finished (not in an "effect"),
31
+ // if the state update was done "conventionally" (by calling `_setNewState()`),
32
+ // React would throw an error about updating state during render.
33
+ // No one knows what the original error message was.
34
+ // Perhaps it's no longer relevant in newer versions of React.
35
+ //
36
+ const USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION = true
37
+
15
38
  function VirtualScroller({
16
39
  as: AsComponent,
17
- items,
40
+ items: itemsProperty,
18
41
  itemComponent: Component,
19
42
  itemComponentProps,
20
43
  // `estimatedItemHeight` property name is deprecated,
@@ -52,7 +75,7 @@ function VirtualScroller({
52
75
 
53
76
  // Create a `VirtualScroller` instance.
54
77
  const virtualScroller = useVirtualScroller({
55
- items,
78
+ items: itemsProperty,
56
79
  // `estimatedItemHeight` property name is deprecated,
57
80
  // use `getEstimatedItemHeight` property instead.
58
81
  estimatedItemHeight,
@@ -90,11 +113,13 @@ function VirtualScroller({
90
113
  // This way, React will re-render the component on every state update.
91
114
  const {
92
115
  getState,
93
- updateState
116
+ updateState,
117
+ getNextState
94
118
  } = useState({
95
119
  initialState: _initialState,
96
120
  onRender: virtualScroller.onRender,
97
- items
121
+ itemsProperty,
122
+ USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION
98
123
  })
99
124
 
100
125
  // Use custom (external) state storage in the `VirtualScroller`.
@@ -121,24 +146,31 @@ function VirtualScroller({
121
146
  // Cache per-item `setItemState` functions' "references"
122
147
  // so that item components don't get re-rendered needlessly.
123
148
  const getSetItemState = useSetItemState({
124
- items,
149
+ initialItemsCount: itemsProperty.length,
125
150
  virtualScroller
126
151
  })
127
152
 
128
- // Cache per-item `onItemHeightChange` functions' "references"
153
+ // Cache per-item `onItemHeightDidChange` functions' "references"
129
154
  // so that item components don't get re-rendered needlessly.
130
- const getOnItemHeightChange = useOnItemHeightChange({
131
- items,
155
+ const getOnItemHeightDidChange = useOnItemHeightDidChange({
156
+ initialItemsCount: itemsProperty.length,
132
157
  virtualScroller
133
158
  })
134
159
 
135
- // Detect if `items` have changed.
136
- useHandleItemsChange(items, {
160
+ // Calls `.setItems()` if `items` property has changed.
161
+ useHandleItemsPropertyChange(itemsProperty, {
137
162
  virtualScroller,
138
163
  // `preserveScrollPosition` property name is deprecated,
139
164
  // use `preserveScrollPositionOnPrependItems` property instead.
140
165
  preserveScrollPosition,
141
166
  preserveScrollPositionOnPrependItems,
167
+ nextItems: getNextState().items
168
+ })
169
+
170
+ // Updates `key`s if item indexes have changed.
171
+ useHandleItemIndexesChange({
172
+ virtualScroller,
173
+ itemsBeingRendered: getNextState().items,
142
174
  updateItemKeysForNewItems
143
175
  })
144
176
 
@@ -177,15 +209,15 @@ function VirtualScroller({
177
209
 
178
210
  const style = useStyle({
179
211
  tbody,
180
- virtualScroller
212
+ getNextState
181
213
  })
182
214
 
183
215
  const {
184
- items: renderedItems,
216
+ items: currentItems,
185
217
  itemStates,
186
218
  firstShownItemIndex,
187
219
  lastShownItemIndex
188
- } = virtualScroller.getState()
220
+ } = getNextState()
189
221
 
190
222
  return (
191
223
  <AsComponent
@@ -193,7 +225,7 @@ function VirtualScroller({
193
225
  ref={container}
194
226
  className={className}
195
227
  style={style}>
196
- {renderedItems.map((item, i) => {
228
+ {currentItems.map((item, i) => {
197
229
  if (i >= firstShownItemIndex && i <= lastShownItemIndex) {
198
230
  // * Passing `item` as `children` property is legacy and is deprecated.
199
231
  // If version `2.x` is published in some hypothetical future,
@@ -219,7 +251,8 @@ function VirtualScroller({
219
251
  state={itemStates && itemStates[i]}
220
252
  setState={getSetItemState(i)}
221
253
  onStateChange={getSetItemState(i)}
222
- onHeightChange={getOnItemHeightChange(i)}>
254
+ onHeightChange={getOnItemHeightDidChange(i)}
255
+ onHeightDidChange={getOnItemHeightDidChange(i)}>
223
256
  {item}
224
257
  </Component>
225
258
  )
@@ -0,0 +1,49 @@
1
+ import { useRef } from 'react'
2
+
3
+ // If the order of the `items` changes, or new `items` get prepended resulting in a "shift":
4
+ //
5
+ // * Re-generate the React `key` prefix for item elements
6
+ // so that all item components are re-rendered for the new `items` list.
7
+ // That's because item components may have their own internal state,
8
+ // and simply passing another `item` property for an item component
9
+ // might result in bugs, which React would do with its "re-using" policy
10
+ // if the unique `key` workaround hasn't been used.
11
+ //
12
+ export default function useHandleItemIndexesChange({
13
+ virtualScroller,
14
+ itemsBeingRendered,
15
+ updateItemKeysForNewItems
16
+ }) {
17
+ const previousItemsBeingRenderedRef = useRef(itemsBeingRendered)
18
+ const previousItemsBeingRendered = previousItemsBeingRenderedRef.current
19
+ const haveItemsChanged = itemsBeingRendered !== previousItemsBeingRendered
20
+ previousItemsBeingRenderedRef.current = itemsBeingRendered
21
+
22
+ if (haveItemsChanged) {
23
+ let shouldUpdateItemKeys = true
24
+
25
+ const itemsDiff = virtualScroller.getItemsDiff(previousItemsBeingRendered, itemsBeingRendered)
26
+ // `itemsDiff` will be `undefined` in case of a non-incremental items list change.
27
+ if (itemsDiff) {
28
+ const {
29
+ prependedItemsCount,
30
+ appendedItemsCount
31
+ } = itemsDiff
32
+ if (prependedItemsCount === 0 && appendedItemsCount === 0) {
33
+ // The items order hasn't changed.
34
+ // No need to re-generate the `key` prefix.
35
+ shouldUpdateItemKeys = false
36
+ }
37
+ else if (prependedItemsCount === 0 && appendedItemsCount > 0) {
38
+ // The item order hasn't changed.
39
+ // No need to re-generate the `key` prefix.
40
+ shouldUpdateItemKeys = false
41
+ }
42
+ }
43
+
44
+ // Update React element `key`s for the new set of `items`.
45
+ if (shouldUpdateItemKeys) {
46
+ updateItemKeysForNewItems()
47
+ }
48
+ }
49
+ }
@@ -1,6 +1,6 @@
1
1
  import { useRef } from 'react'
2
2
 
3
- // If new `items` are passed:
3
+ // If new `items` property is passed:
4
4
  //
5
5
  // * Store the scroll Y position for the first one of the current items
6
6
  // so that it could potentially (in some cases) be restored after the
@@ -8,26 +8,14 @@ import { useRef } from 'react'
8
8
  //
9
9
  // * Call `VirtualScroller.setItems()` function.
10
10
  //
11
- // * Re-generate the React `key` prefix for item elements
12
- // so that all item components are re-rendered for the new `items` list.
13
- // That's because item components may have their own internal state,
14
- // and simply passing another `item` property for an item component
15
- // might result in bugs, which React would do with its "re-using" policy
16
- // if the unique `key` workaround hasn't been used.
17
- //
18
- export default function useHandleItemsChange(items, {
11
+ export default function useHandleItemsPropertyChange(itemsProperty, {
19
12
  virtualScroller,
20
13
  // `preserveScrollPosition` property name is deprecated,
21
14
  // use `preserveScrollPositionOnPrependItems` property instead.
22
15
  preserveScrollPosition,
23
16
  preserveScrollPositionOnPrependItems,
24
- updateItemKeysForNewItems
17
+ nextItems
25
18
  }) {
26
- const {
27
- items: renderedItems,
28
- firstShownItemIndex
29
- } = virtualScroller.getState()
30
-
31
19
  // During render, check if the `items` list has changed.
32
20
  // If it has, capture the Y scroll position and updated item element `key`s.
33
21
 
@@ -72,13 +60,16 @@ export default function useHandleItemsChange(items, {
72
60
  // scroll Y position is captured while the "Show previous" button
73
61
  // is still being shown.
74
62
 
75
- const previousItems = useRef(items)
76
- const hasItemsPropertyChanged = items !== previousItems.current
77
- previousItems.current = items
63
+ const previousItemsProperty = useRef(itemsProperty)
64
+ const hasItemsPropertyChanged = itemsProperty !== previousItemsProperty.current
65
+ previousItemsProperty.current = itemsProperty
66
+
78
67
  if (hasItemsPropertyChanged) {
79
- let itemsHaveChanged = true
80
- let shouldUpdateItemKeys = true
81
- const itemsDiff = virtualScroller.getItemsDiff(renderedItems, items)
68
+ let shouldUpdateItems = true
69
+
70
+ // Analyze the upcoming `items` change.
71
+ const itemsDiff = virtualScroller.getItemsDiff(nextItems, itemsProperty)
72
+
82
73
  // `itemsDiff` will be `undefined` in case of a non-incremental items list change.
83
74
  if (itemsDiff) {
84
75
  const {
@@ -86,30 +77,21 @@ export default function useHandleItemsChange(items, {
86
77
  appendedItemsCount
87
78
  } = itemsDiff
88
79
  if (prependedItemsCount === 0 && appendedItemsCount === 0) {
89
- // The items haven't changed. No need to re-generate
90
- // the `key` prefix or to snapshot the Y scroll position.
91
- itemsHaveChanged = false
92
- shouldUpdateItemKeys = false
93
- }
94
- else if (prependedItemsCount === 0 && appendedItemsCount > 0) {
95
- // Just some items got appended. No need to re-generate
96
- // the `key` prefix or to snapshot the Y scroll position.
97
- shouldUpdateItemKeys = false
80
+ // The items order hasn't changed.
81
+ // No need to update them in `VirtualScroller` or to snapshot the Y scroll position.
82
+ shouldUpdateItems = false
98
83
  }
99
84
  }
100
85
 
101
- if (itemsHaveChanged) {
102
- // Set the new `items`.
103
- virtualScroller.setItems(items, {
86
+ if (shouldUpdateItems) {
87
+ // Request to update the `items` in `VirtualScroller`.
88
+ // This will result in a `setState()` call.
89
+ // The new items won't be rendered until that state update is applied.
90
+ virtualScroller.setItems(itemsProperty, {
104
91
  // `preserveScrollPosition` property name is deprecated,
105
92
  // use `preserveScrollPositionOnPrependItems` property instead.
106
93
  preserveScrollPositionOnPrependItems: preserveScrollPositionOnPrependItems || preserveScrollPosition
107
94
  })
108
-
109
- // Update React element `key`s for the new set of `items`.
110
- if (shouldUpdateItemKeys) {
111
- updateItemKeysForNewItems()
112
- }
113
95
  }
114
96
  }
115
97
  }
@@ -1,22 +1,22 @@
1
1
  import { useMemo, useRef, useCallback } from 'react'
2
2
 
3
- export default function useOnItemHeightChange({
4
- items,
3
+ export default function useOnItemHeightDidChange({
4
+ initialItemsCount,
5
5
  virtualScroller
6
6
  }) {
7
7
  // Only compute the initial cache value once.
8
8
  const initialCacheValue = useMemo(() => {
9
- return new Array(items.length)
9
+ return new Array(initialItemsCount)
10
10
  }, [])
11
11
 
12
12
  // Handler functions cache.
13
13
  const cache = useRef(initialCacheValue)
14
14
 
15
- // Caches per-item `onItemHeightChange` functions' "references"
15
+ // Caches per-item `onItemHeightDidChange` functions' "references"
16
16
  // so that item components don't get re-rendered needlessly.
17
- const getOnItemHeightChange = useCallback((i) => {
17
+ const getOnItemHeightDidChange = useCallback((i) => {
18
18
  if (!cache.current[i]) {
19
- cache.current[i] = () => virtualScroller.onItemHeightChange(i)
19
+ cache.current[i] = () => virtualScroller.onItemHeightDidChange(i)
20
20
  }
21
21
  return cache.current[i]
22
22
  }, [
@@ -24,5 +24,5 @@ export default function useOnItemHeightChange({
24
24
  cache
25
25
  ])
26
26
 
27
- return getOnItemHeightChange
27
+ return getOnItemHeightDidChange
28
28
  }
@@ -1,12 +1,12 @@
1
1
  import { useMemo, useRef, useCallback } from 'react'
2
2
 
3
3
  export default function useSetItemState({
4
- items,
4
+ initialItemsCount,
5
5
  virtualScroller
6
6
  }) {
7
7
  // Only compute the initial cache value once.
8
8
  const initialCacheValue = useMemo(() => {
9
- return new Array(items.length)
9
+ return new Array(initialItemsCount)
10
10
  }, [])
11
11
 
12
12
  // Handler functions cache.