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.
- package/CHANGELOG.md +5 -0
- package/README.md +9 -9
- package/bundle/virtual-scroller-dom.js +1 -1
- package/bundle/virtual-scroller-dom.js.map +1 -1
- package/bundle/virtual-scroller-react.js +1 -1
- package/bundle/virtual-scroller-react.js.map +1 -1
- package/bundle/virtual-scroller.js +1 -1
- package/bundle/virtual-scroller.js.map +1 -1
- package/commonjs/DOM/ItemsContainer.js +22 -9
- package/commonjs/DOM/ItemsContainer.js.map +1 -1
- package/commonjs/DOM/VirtualScroller.js +13 -1
- package/commonjs/DOM/VirtualScroller.js.map +1 -1
- package/commonjs/ItemHeights.js +5 -5
- package/commonjs/ItemHeights.js.map +1 -1
- package/commonjs/VirtualScroller.js +21 -4
- package/commonjs/VirtualScroller.js.map +1 -1
- package/commonjs/VirtualScroller.layout.js +22 -22
- package/commonjs/VirtualScroller.layout.js.map +1 -1
- package/commonjs/VirtualScroller.state.js +1 -1
- package/commonjs/VirtualScroller.state.js.map +1 -1
- package/commonjs/react/VirtualScroller.js +53 -20
- package/commonjs/react/VirtualScroller.js.map +1 -1
- package/commonjs/react/useHandleItemIndexesChange.js +53 -0
- package/commonjs/react/useHandleItemIndexesChange.js.map +1 -0
- package/commonjs/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +21 -40
- package/commonjs/react/useHandleItemsPropertyChange.js.map +1 -0
- package/commonjs/react/{useOnItemHeightChange.js → useOnItemHeightDidChange.js} +9 -9
- package/commonjs/react/useOnItemHeightDidChange.js.map +1 -0
- package/commonjs/react/useSetItemState.js +2 -2
- package/commonjs/react/useSetItemState.js.map +1 -1
- package/commonjs/react/useState.js +47 -69
- package/commonjs/react/useState.js.map +1 -1
- package/commonjs/react/useStyle.js +4 -4
- package/commonjs/react/useStyle.js.map +1 -1
- package/dom/index.d.ts +1 -1
- package/index.d.ts +1 -1
- package/modules/DOM/ItemsContainer.js +22 -9
- package/modules/DOM/ItemsContainer.js.map +1 -1
- package/modules/DOM/VirtualScroller.js +13 -1
- package/modules/DOM/VirtualScroller.js.map +1 -1
- package/modules/ItemHeights.js +5 -5
- package/modules/ItemHeights.js.map +1 -1
- package/modules/VirtualScroller.js +15 -4
- package/modules/VirtualScroller.js.map +1 -1
- package/modules/VirtualScroller.layout.js +22 -22
- package/modules/VirtualScroller.layout.js.map +1 -1
- package/modules/VirtualScroller.state.js +1 -1
- package/modules/VirtualScroller.state.js.map +1 -1
- package/modules/react/VirtualScroller.js +52 -21
- package/modules/react/VirtualScroller.js.map +1 -1
- package/modules/react/useHandleItemIndexesChange.js +45 -0
- package/modules/react/useHandleItemIndexesChange.js.map +1 -0
- package/modules/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +20 -39
- package/modules/react/useHandleItemsPropertyChange.js.map +1 -0
- package/modules/react/{useOnItemHeightChange.js → useOnItemHeightDidChange.js} +8 -8
- package/modules/react/useOnItemHeightDidChange.js.map +1 -0
- package/modules/react/useSetItemState.js +2 -2
- package/modules/react/useSetItemState.js.map +1 -1
- package/modules/react/useState.js +48 -70
- package/modules/react/useState.js.map +1 -1
- package/modules/react/useStyle.js +4 -4
- package/modules/react/useStyle.js.map +1 -1
- package/package.json +1 -1
- package/source/DOM/ItemsContainer.js +13 -3
- package/source/DOM/VirtualScroller.js +11 -1
- package/source/ItemHeights.js +5 -5
- package/source/VirtualScroller.js +12 -3
- package/source/VirtualScroller.layout.js +22 -22
- package/source/VirtualScroller.state.js +1 -1
- package/source/react/VirtualScroller.js +50 -17
- package/source/react/useHandleItemIndexesChange.js +49 -0
- package/source/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +20 -38
- package/source/react/{useOnItemHeightChange.js → useOnItemHeightDidChange.js} +7 -7
- package/source/react/useSetItemState.js +2 -2
- package/source/react/useState.js +57 -72
- package/source/react/useStyle.js +2 -2
- package/commonjs/react/useHandleItemsChange.js.map +0 -1
- package/commonjs/react/useOnItemHeightChange.js.map +0 -1
- package/modules/react/useHandleItemsChange.js.map +0 -1
- 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 `.
|
|
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 `.
|
|
175
|
+
// updating item heights externally via `.onItemHeightDidChange(i)`.
|
|
176
176
|
//
|
|
177
|
-
// If, for example, an item height was updated externally via `.
|
|
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 `.
|
|
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
|
-
// `.
|
|
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 `.
|
|
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 `.
|
|
294
|
-
// But, as the first `.
|
|
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 `.
|
|
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 `.
|
|
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 `
|
|
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.
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
395
|
-
// But, as the first `.
|
|
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 `.
|
|
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 `.
|
|
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
|
-
// `.
|
|
410
|
-
return warn('The item is no longer rendered. This is not necessarily a bug, and could happen, for example, when
|
|
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(`"
|
|
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
|
|
11
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
149
|
+
initialItemsCount: itemsProperty.length,
|
|
125
150
|
virtualScroller
|
|
126
151
|
})
|
|
127
152
|
|
|
128
|
-
// Cache per-item `
|
|
153
|
+
// Cache per-item `onItemHeightDidChange` functions' "references"
|
|
129
154
|
// so that item components don't get re-rendered needlessly.
|
|
130
|
-
const
|
|
131
|
-
|
|
155
|
+
const getOnItemHeightDidChange = useOnItemHeightDidChange({
|
|
156
|
+
initialItemsCount: itemsProperty.length,
|
|
132
157
|
virtualScroller
|
|
133
158
|
})
|
|
134
159
|
|
|
135
|
-
//
|
|
136
|
-
|
|
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
|
-
|
|
212
|
+
getNextState
|
|
181
213
|
})
|
|
182
214
|
|
|
183
215
|
const {
|
|
184
|
-
items:
|
|
216
|
+
items: currentItems,
|
|
185
217
|
itemStates,
|
|
186
218
|
firstShownItemIndex,
|
|
187
219
|
lastShownItemIndex
|
|
188
|
-
} =
|
|
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
|
-
{
|
|
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={
|
|
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`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
76
|
-
const hasItemsPropertyChanged =
|
|
77
|
-
|
|
63
|
+
const previousItemsProperty = useRef(itemsProperty)
|
|
64
|
+
const hasItemsPropertyChanged = itemsProperty !== previousItemsProperty.current
|
|
65
|
+
previousItemsProperty.current = itemsProperty
|
|
66
|
+
|
|
78
67
|
if (hasItemsPropertyChanged) {
|
|
79
|
-
let
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
90
|
-
//
|
|
91
|
-
|
|
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 (
|
|
102
|
-
//
|
|
103
|
-
|
|
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
|
|
4
|
-
|
|
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(
|
|
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 `
|
|
15
|
+
// Caches per-item `onItemHeightDidChange` functions' "references"
|
|
16
16
|
// so that item components don't get re-rendered needlessly.
|
|
17
|
-
const
|
|
17
|
+
const getOnItemHeightDidChange = useCallback((i) => {
|
|
18
18
|
if (!cache.current[i]) {
|
|
19
|
-
cache.current[i] = () => virtualScroller.
|
|
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
|
|
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
|
-
|
|
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(
|
|
9
|
+
return new Array(initialItemsCount)
|
|
10
10
|
}, [])
|
|
11
11
|
|
|
12
12
|
// Handler functions cache.
|