Fix VirtualizedList skips items for offset measuring when the user scrolls very fast (3rd of 4 problems that cause #1254) #2414
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
I found 4 problems that cause the Inverted VirtualizedList to go wild (#1254). This PR will explain and fix the 3rd of 4 problems found.
If we fix the following 4 problems, issue #1254 will be fixed and FlatList’s scroll will be more stable:
Update:
PR 2, 3, and 4 will fix 'Flatlist with expensive items breaks scroll' issue. PR 1 is enough to fix the Inverted Flatlist issue if your Flatlist's items are cheap to mount.
$lead_spacer expands scroll artificially when Inverted VirtualizedList is mounting new items —> Problem Explanation and Solution in this PR.
VirtualizedList skip items for offset measuring when the user scrolls very fast while new items are mounted and measured (this happens also for normal lists) —> Problem Explanation and Solution below.
VirtualizedList gets offsets equal to zero for items that are not the list's first item (this happens also for normal lists) —> Problem Explanation and Solution in this PR.
Video of Inverted FlatList with all problems fixed:
2022-10-23.17-49-22.mp4
3rd Problem
VirtualizedList skip items for offset measuring when the user scrolls very fast while new items are mounted and measured (this happens also for normal lists, 3rd of 4 problems that cause #1254)
There are two important variables (react states of VirtualizedList) that determine the virtual area:
first
andlast
. These variables represent a range of values from thedata
array (which is raw information of items provided to FlatList asdata
prop) that we use to render items on the virtual area.On every scroll,
first
andlast
get updated according to two priority updates: Low Priority and High Priority.Low Priority updates help us to wait for new items to be mounted and measured. These updates are scheduled in a queue and are useful when the user is scrolling on unmeasured area.
High Priority updates help us to mount items as fast as possible. These updates occur when users scroll fast: if a High Priority update is triggered, every Low Priority update on the queue is canceled.
When the user scrolls up to unmeasured area, ideally (on every update) the prev
last
should be greater than the nextfirst
. This ensures that every item is measured and saved in_frames
, like this:But If we scroll very fast towards unmeasured area and the list is still rendering new items, a race condition happens between measuring new items in
onLayout
and the High Priority update. This is becauseonLayout
(executed on unmeasured items) takes some time to return the offset value that we need after the item is mounted. If High Priority wins, the nextfirst
will be greater than prevlast
, and as a consequence, some items will be skipped for measuring:More than one item can be skipped, which makes the problem worse. The more items are skipped to be measured, the greater the probability to experiment scroll issues (like weird jumps or white space in the visible area).
Solution:
packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js
1.-Description of the first line of code:
We use a variable called
totalCellLength
, it represents the sum of heights of all items measured.If
totalCellLength
is greater than scroll offset + visibleLength / 2 (half of our Visible Area) it means the measure of new items is keeping up with the scroll toward unmeasured area or that we are in an area where the cells had been already measured so we allow High Priority updates as usual:If
totalCellLength
is not greater than scroll offset + visibleLength / 2 thenVirtulizedList
prevents High Priority updates, allowing Low Priority updates on queue to complete so measure of items can catch up with scroll and prevents subsequent updates where nextfirst
is greater than prevlast
:Why visibleLength / 2?
It represents a balanced ‘bar check’ to be sure that all items are gonna be measured and mounted as fast as possible (when user scrolls quickly towards unmeasured area). I tested with other two approaches:
With offset + visibleLength (top of Visible Area) we set the ‘bar check’ too high: the check will be false as soon as a new item is mounted. This denies any margin to make High Priority Updates when users keep scrolling up to unmeasured area.
With totalCellLength > offset (bottom of Visible Area), the check will be true only if one new item is measured in our visible area. This will trigger more High Priority updates than it should, Low Priority updates will be canceled more often, and we lose the certainty that no items are gonna skip measure.
I tested previous scenarios and I conclude scroll offset + visibleLength / 2 is the fastest way to scroll safely toward unmeasured area.
2.-Description of the second line of code:
Prevents the checking of the first line of code (allows High Priority updates as usual) if the FlatList doesn't have enough rendered items to fill both the Visible Area and the
initialNumToRender
items (the number of items at the beginning of FlatList that never unmount). This preserves a fast list initialization.