react-native: [VirtualList] Render blockages for multiple seconds

Is 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 with SectionLists (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…

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 16 (1 by maintainers)

Commits related to this issue

Most upvoted 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.

I also have a similar issue. My SectionList blocks all interactions for 5 seconds or more when rendering the first time, and my section items are PureComponents. The screen simply freezes.

If I add the getItemLayout function prop, then it renders much faster and after 1 second I can interact with the screen. However, since my rows are not all the same height and I can’t calculate the exact height of each item, it messes up with scrollToLocation. Moreover, if I scroll too much, the list starts rendering outside the viewable area.

FlatList and SectionList is bad performance. Try this,please. may be it is a surprise for you

https://github.com/bolan9999/react-native-largelist