You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Public API to forget (or re-measure) one item's measured size
#1362
An item's content changes shape while its key stays the same: a group row expands or collapses, a message is edited, a card swaps its body. The size virtual-core has cached for that key is now wrong.
When the row is rendered, the ResizeObserver picks the change up. When it is not, the cached size stays in the layout until the row is rendered again, and since #1183 the default measureElement answers the mount-time measurement from itemSizeCache when it has the key, so the stale size is also what the row is placed with until its ResizeObserver entry arrives. Meanwhile every offset below the item is off: scrollToIndex to a later row lands short or long until the item has been measured again, and when it is, the correction follows the re-measurement anchoring rule (only items entirely above the viewport, not while scrolling backward) rather than the first-measurement one, so it shows as a jump.
What we want to say is "the size you have for this item is no longer valid": use estimateSize for it until it is measured, measure it now if it is rendered, and treat that measurement as a first one.
What is possible today
resizeItem(index, size) sets a size, but there is no way to unset one. resizeItem(index, estimateSize(index)) comes close, except the item still counts as measured: the anchoring rule above, takeSnapshot() persisting the estimate as a measurement, and the mount-time measurement returning it instead of reading the element.
measure() forgets every item, which costs all the measurements of a long list to fix one item, and moves every row that was measured until they are all measured again.
itemSizeCache is a public Map (since feat(virtual-core): skip sync DOM read in measureElement when cached size exists #1183), but writing to it does nothing by itself: the layout memo keys on the private itemSizeCacheVersion (index.ts L485). Bumping that version through the private field is not enough either: the rebuild starts at the private pendingMin, so a write below an index that resizeItem has dirtied since the last read is still ignored:
virtualizer.getVirtualItems()virtualizer.resizeItem(10,100)virtualizer.itemSizeCache.set(virtualizer.options.getItemKey(3),200)virtualizer.itemSizeCacheVersion++virtualizer.getTotalSize()// 5050, expected 5200 (100 items at 50px)
Changing the item's key works, but the key is usually also the framework's element key, so the row remounts and loses its state, a changed first or last key goes through the prepend and append handling in setOptions, and the old size stays in itemSizeCache until the next measure().
We maintain a Blazor wrapper over virtual-core, and it currently does the private-field version of this. For an item that is not rendered it deletes the key from itemSizeCache and bumps itemSizeCacheVersion; for a rendered row it reads the size off the element and applies it through resizeItem; and for a size it has to keep under a key that is not at a known index while the item set is being replaced, it writes itemSizeCache directly and resets pendingMin too. It works, but only because of how the internals happen to be ordered today.
Proposal
A counterpart to resizeItem, for example:
resetItemSize: (index: number)=>void
Removes the item's entry from itemSizeCache, and lowers pendingMin and bumps the version the way resizeItem does, so the layout falls back to estimateSize(index) for it.
Applies the same scroll compensation resizeItem would for the size change (measured size to estimate) when the item is above the viewport.
If the item's element is registered and connected, measures it right away instead, so a rendered row never shows the estimate; that measurement counts as a first measurement.
Notifies like resizeItem.
Open questions:
Name and shape: a new resetItemSize(index), or measure() taking an optional index (or indexes), since measure() already means "reset measurements".
Index or key: by index matches resizeItem and the rest of the API. By key also covers an item that is not at a known index right now, for example while the item set is being replaced; that is the case where our wrapper needs to keep or forget a size today, but it may be too specific to us.
Related
A public way to read one item's current layout by index would help too. getMeasurements() is private, and the public measurementsCache field lags until the memo runs (see #1361). react-virtual reads that field itself for directDomUpdates (react-virtual/src/index.tsx L240), and #1021 shows consumers reading it as well. Happy to split that into its own discussion.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
The use case
An item's content changes shape while its key stays the same: a group row expands or collapses, a message is edited, a card swaps its body. The size virtual-core has cached for that key is now wrong.
When the row is rendered, the ResizeObserver picks the change up. When it is not, the cached size stays in the layout until the row is rendered again, and since #1183 the default
measureElementanswers the mount-time measurement fromitemSizeCachewhen it has the key, so the stale size is also what the row is placed with until its ResizeObserver entry arrives. Meanwhile every offset below the item is off:scrollToIndexto a later row lands short or long until the item has been measured again, and when it is, the correction follows the re-measurement anchoring rule (only items entirely above the viewport, not while scrolling backward) rather than the first-measurement one, so it shows as a jump.What we want to say is "the size you have for this item is no longer valid": use
estimateSizefor it until it is measured, measure it now if it is rendered, and treat that measurement as a first one.What is possible today
resizeItem(index, size)sets a size, but there is no way to unset one.resizeItem(index, estimateSize(index))comes close, except the item still counts as measured: the anchoring rule above,takeSnapshot()persisting the estimate as a measurement, and the mount-time measurement returning it instead of reading the element.measure()forgets every item, which costs all the measurements of a long list to fix one item, and moves every row that was measured until they are all measured again.itemSizeCacheis a publicMap(since feat(virtual-core): skip sync DOM read in measureElement when cached size exists #1183), but writing to it does nothing by itself: the layout memo keys on the privateitemSizeCacheVersion(index.ts L485). Bumping that version through the private field is not enough either: the rebuild starts at the privatependingMin, so a write below an index thatresizeItemhas dirtied since the last read is still ignored:Changing the item's key works, but the key is usually also the framework's element key, so the row remounts and loses its state, a changed first or last key goes through the prepend and append handling in
setOptions, and the old size stays initemSizeCacheuntil the nextmeasure().We maintain a Blazor wrapper over virtual-core, and it currently does the private-field version of this. For an item that is not rendered it deletes the key from
itemSizeCacheand bumpsitemSizeCacheVersion; for a rendered row it reads the size off the element and applies it throughresizeItem; and for a size it has to keep under a key that is not at a known index while the item set is being replaced, it writesitemSizeCachedirectly and resetspendingMintoo. It works, but only because of how the internals happen to be ordered today.Proposal
A counterpart to
resizeItem, for example:itemSizeCache, and lowerspendingMinand bumps the version the wayresizeItemdoes, so the layout falls back toestimateSize(index)for it.resizeItemwould for the size change (measured size to estimate) when the item is above the viewport.resizeItem.Open questions:
resetItemSize(index), ormeasure()taking an optional index (or indexes), sincemeasure()already means "reset measurements".resizeItemand the rest of the API. By key also covers an item that is not at a known index right now, for example while the item set is being replaced; that is the case where our wrapper needs to keep or forget a size today, but it may be too specific to us.Related
A public way to read one item's current layout by index would help too.
getMeasurements()is private, and the publicmeasurementsCachefield lags until the memo runs (see #1361). react-virtual reads that field itself fordirectDomUpdates(react-virtual/src/index.tsx L240), and #1021 shows consumers reading it as well. Happy to split that into its own discussion.If the direction sounds right, I can open a PR.
All reactions