[VirtualList] Render blockages for multiple seconds
See original GitHub issueIs this a bug report?
Yes.
Have you read the Contributing Guidelines?
Yes.
Environment
Environment: OS: macOS Sierra 10.12.6 Node: 6.10.2 Yarn: 0.15.1 npm: 4.6.1 Watchman: 4.1.0 Xcode: Xcode 9.1 Build version 9B55 Android Studio: 2.3 AI-162.4069837
Packages: (wanted => installed) react: ^16.0.0 => 16.0.0 react-native: file:…/…/react-native => 0.50.2
Steps to Reproduce
When you use a VirtualList with refresh component, and then hide the refresh component to show a list of variable sized elements (ie, a section list with headers that are differently-sized than the elements), you can have extraordinarily-long delays during the second rendering frame.
Expected Behavior
It renders quickly.
Reproducible Demo
You can see a repro case here: https://snack.expo.io/HkRgOcEZG
After the simulated 500ms load, it proceeds to render item 0. Then it gets “stuck” for a 5+ seconds before rendering items 1-to-N. You can make it even worse by uncommenting maxToRenderPerBatch={1} (ie, rendering in smaller batches makes it even slower, and makes it take even longer to render an initial batch of items!)
===============
My long-breakdown of what’s going on from poking around with debug statements:
When the list loads and the refresh indicator hides, in _scheduleCellsToRenderUpdate
the distTop
is negative, and the velocity is negative (due to the refresh indicator being hidden). Because it’s negative, it triggers a “hi priority” updates whenever _scheduleCellsToRenderUpdate
is called. Even when there are more than enough elements on screen. This is Bug 1 (ie, logically, the user is not scrolling, so I’m not sure this should be a high priority update).
After the first cell is laid out in _onCellLayout
(ie, the first initialNumToRender
cells) and it has an average cell size , it calls _onCellLayout->_scheduleCellsToRenderUpdate->_updateCellsToRender
, and uses computeWindowedRenderLimits
to compute the new state of the {first, last}
render window. And computeWindowedRenderLimits
, uses the average cell size to figure out how many cells are visible (and how many should be rendered for the overscan), and computes the new {first, last}
.
This setState
with new state triggers a call to componentDidUpdate
, which due to the potential layout of new cells in the {first, last}
window, triggers another call to _scheduleCellsToRenderUpdate
.
This runs through the whole loop shebang again, computing another high priority update of computeWindowedRenderLimits
…but this time the average cell size has changed (due to some new cells, I think? Not entirely sure…). This causes it to generate a different {first, last}
(sometimes larger, sometimes smaller), which then triggers the whole shebang all over again when componentDidUpdate
gets called.
And unfortunately, after the first render…the system proceeds to run through this componentDidUpdate->...->setState->componentDidUpdate
loop many many times, without having time to actually push the render state to the screen. And each time through, the schedule call sees a high-priority update due to the negative distTop
(due to the refresh control), so it can’t relax.
This loop can run many times before it “settles down” on an final render window, leading to a visible lag in rendering all these items. In fact, smaller maxToRenderPerBatch
will make this take even longer.
During all of this cycling, two things are noticeable in the UI:
- my Touchable elements don’t actually respond, and I can’t actually click on the elements yet.
- The initial render is complete (with
initialNumToRender
), but the second batch never gets a chance to render to screen. This causes my List to be stuck with an incomplete list for multiple seconds, before the internal loop finally “settles”, gives up on state-changes and high-priority updates, and lets the system render the rest of my items properly.
Unfortunately, I’m not sure of a correct fix here. Workarounds include:
- Passing a pixel-perfect
getItemLayout
, though this is super-tricky to get right withSectionLists
(and I failed last time I tried). - Disabling virtualization, since there is no longer a ‘render window’ to constantly update (and fail to update accurately), though this causes my lower-end clients to OOM.
Other suggestions/fixes would be appreciated…
Issue Analytics
- State:
- Created 6 years ago
- Reactions:1
- Comments:16 (1 by maintainers)
Top GitHub Comments
Hi, sorry to bother you guys, but I am having a kinda similar problem and I believe that computeWindowedRenderLimits in VirtualizeUtils.js has a mistake.
I believe the line that is wrong is this one:
const overscanLength = (windowSize - 1) * visibleLength;
VisibleLength is the visible area of the virtualizedlist. It will give a very big overscanLength that will render much more cells than the specified in the windowSize.
So if we replace that line for this one:
const overscanLength = (windowSize - 1) * getFrameMetricsApprox(0).length;
Then the windowSize parameter is respected. Maybe this is the problem that you guys are experiencing?
@jochem725 , probably it will take a bit more time on scrolling because it’s now respecting the windowSize, which means the number of cells already rendered before a scroll is less than before. You can try playing with windowSize parameter to find a nice compromise between render/scroll performance.