Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FlatList performance: Scrolling is buggy and not smooth when virtualization is enabled #1337

Closed
brunolemos opened this issue May 3, 2019 · 32 comments

Comments

@brunolemos
Copy link
Contributor

brunolemos commented May 3, 2019

The problem

It have a vertical FlatList with not that many items (~150). When virtualization is enabled and I scroll, there are lot's of FPS loss, I can see the lag clearly. Every time I scroll a little bit and a new dom element is added by the virtualization it happens.

On iOS simulator it's fast and smooth. On chrome is the worse, firefox is less bad but still not good.

If I use disableVirtualization, then it becomes super smooth, but it has its consequences, e.g. switching between screens is much more expensive.

I played with all the virtualization options, didn't help much.

Twitter is quite fast and also has dynamic height, does it use the same FlatList we use? If not, would love if they could open source it.

I've considered react-window but it doesn't seem to support dynamic height (just-in-time measurement) yet. bvaughn/react-window#6. Maybe react-virtualized could help.
The problem is that these external solutions don't support things like scrollToItem, onViewableItemsChanged, etc :(

GIF comparison

With virtualization enabled + getItemLayout forcing height:100

Slow and buggy

Kapture 2019-05-04 at 22 19 59

Without virtualization (disableVirtualization)

Scrolling is fast and smooth (screen change / first render is slower; memory usage is higher)

Kapture 2019-05-04 at 22 22 22

@brunolemos brunolemos reopened this May 4, 2019
@brunolemos
Copy link
Contributor Author

You can close this but I'd appreciate some insights.

@sfcgeorge
Copy link

I also noticed slow FlatList but I don't need dynamic heights so I was able to get good performance. Things to try:

  • Use PureComponent for your items.
  • Set keyExtractor.
  • Set getItemLayout (it's a function so you may be able to calculate dynamic height yourself).

I suspect getItemLayout will fix it for you. Before worrying about how to calculate the height, just set it to something fixed to test if that does solve the performance for you. If yes, then you can work out how to calculate.

I believe FlatList is taken straight from RN so I assume native Views + Yoga perform better than divs + flexbox in browser. Probably not much can be done without rewriting FlatList which I assume is out of scope as RNW tries to stay close to RN. A third party like Twitter could open source theirs (if they have one) but you'd have to ask them ¯⁠\_(ツ)_/⁠¯

@brunolemos
Copy link
Contributor Author

brunolemos commented May 5, 2019

PureComponent, keyExtractor

Thanks but I use that already.

getItemLayout

Just tried, unfortunately didn't help. Used getItemLayout to fix the height in 100. When scrolling, it still makes small jumps when the virtualization adds new items. The only thing that worked so far was disableVirtualization.

@brunolemos
Copy link
Contributor Author

brunolemos commented May 5, 2019

I've just edited the first comment with a gif comparison, check it out.

@brunolemos brunolemos changed the title FlatList performance: Scrolling is choppy (not smooth) when virtualized FlatList performance: Scrolling is buggy and not smooth when virtualization is enabled May 5, 2019
@derekblank
Copy link

@brunolemos I have experienced this when a <FlatList /> nested within a ScrollView contains many subviews (especially subviews containing images).

The example provided by RNW wraps FlatList with createAnimatedComponent from the Animated API, and also uses some other methods that may help you.

Also worth noting that while disableVirtualization is still active in RNW, it is deprecated in RN.

brunolemos added a commit to devhubapp/devhub that referenced this issue May 7, 2019
@sfcgeorge
Copy link

I think the answer is use React.PureComponent for all renderItem, ItemSeparatorComponent, ListHeaderComponent, etc. The example's item component uses PureComponent here:

class ItemComponent extends React.PureComponent<{

It certainly seems to fix my code, for both FlatList and SegmentedList. A simpler example:

class FilterItem extends React.PureComponent {
  render() {
    const { item, index } = this.props
    return <Text key={index}>{item}</Text>
  }
}

class FilterSeparator extends React.PureComponent {
  render() {
    return <View />
  }
}

export default class SearchFilters extends React.Component {
  render() {
    return (
      <FlatList
        renderItem={({ item, i }) => <FilterItem item={item} index={i} />}
        ItemSeparatorComponent={FilterSeparator}
      />
    )
  }
}

@brunolemos
Copy link
Contributor Author

brunolemos commented May 7, 2019

I think it's some kind of bug, I already do all these common optimizations. Also there is no ScrollView above it. It works perfectly on ios and android, problem is only on browser.

Here's the source code: https://github.com/devhubapp/devhub/blob/f08c991f9d9ca324c23ebbf92e7a6a1de8428e22/packages/components/src/components/cards/NotificationCards.tsx#L330
You can try locally and remove all the disableVirtualization to see the issue.

@0xpatrickdev
Copy link

I am not entirely certain, but I think it might have something to do with FlatList and VirtualizedList not actually being implemented in RNW yet?

FlatList and VirtualizedList seem to be taken directly from react-native, and do not contain any of the DOM specific customizations like we see in ScrollView, TextInput, and View.

The vendor exports do, however, reference the DOM specific ScrollView and View components, so maybe that means they are actually implemented.

I think the FlatList performance suggestions in this thread are good, and definitely applicable since React is still managing the tree reconciliation, but may be outweighed by the need for a DOM specific optimization. I am not entirely sure what that optimization is, but I think @brunolemos is on to something with react-window.

As an aside, the React.PureComponent optimization really only matters when the component has functional props - i.e. an onPress handler or a navigate prop from react-navigation's withNavigation(). In the example above, FilterSeparator has no props, and FilterItem's props are both strings. In both instances, a shallowCompare would have the same effect as a normal comparison, making the need for a PureComponent unnecessary. The keyExtractor should also try and utilize a unique identifier for the item (i.e. item.id) instead of index, as this could cause unexpected behavior if the order of the items changes.

@0xpatrickdev
Copy link

@brunolemos have you given this a look? https://github.com/Flipkart/recyclerlistview

@mayconmesquita
Copy link

@brunolemos @pcooney10
i use https://github.com/Flipkart/recyclerlistview, the perfomance is so great!

@jsp3536
Copy link

jsp3536 commented Jun 6, 2019

I noticed on iPhone 6s and Samsung Galaxy S8 (using Android 8) scrolling with flatlist is very smooth, but I tried two phones using Android 9 (Samsung Galaxy S10, Essential Phone) and scrolling was very choppy. I switched over to RecycleListView and now all phones scroll very smooth without jank.

@brunolemos
Copy link
Contributor Author

brunolemos commented Jun 6, 2019

@jsp3536 you talking about the web version running on the browser of these devices, right?

I may try RecycleListView soon but I'd really prefer to use react-window once it finishes bvaughn/react-window#6.

RecycleListView seems to be missing a couple things I use but no big deal, e.g. Flipkart/recyclerlistview#258. Also it has a lot of open issues and few activity.

@jsp3536
Copy link

jsp3536 commented Jun 6, 2019

Yes. I am happy with the performance of RecycleListView but I need to checkout react-window

@francescoagati
Copy link

@brunolemos have you try with bounces false scrollEventThrottle 300 maxRenderToBatch 1 and updateCellsBatchingPeriod 100 and removeClippedSuvViews true (i dont know if this is implemented in web)

@RichardLindhout
Copy link
Contributor

What I found is that every time there are extra items loaded the flatlist will re render the whole list. Even if you use purecomponent. I now use a normal scrollview on the web and the performance is much, much better!

@francescoagati
Copy link

What I found is that every time there are extra items loaded the flatlist will re render the whole list. Even if you use purecomponent. I now use a normal scrollview on the web and the performance is much, much better!

do you use keys?

@francescoagati
Copy link

and keys extractor?

@RichardLindhout
Copy link
Contributor

@francescoagati Yes, you should try to make a flatlist and log the render function of the item with the index you will see that it will log every time extra items are added, instead of only the newly rendered ones

@necolas
Copy link
Owner

necolas commented Jun 28, 2019

FlatList wasn't built to support the web, where the solution would probably have to involve juggling a bunch of web APIs to try to find extra performance from somewhere. I don't think supporting the web is a priority at the moment (cc @sahrens), but FB might eventually have a cross-platform solution and Google is working on a built-in virtualizer that might be something we can use one day.

@brunolemos
Copy link
Contributor Author

brunolemos commented Jul 4, 2019

I am not sure the problem is with FlatList.

I replaced it with react-window's FixedSizeList and I had pretty similar results.

Maybe there's something wrong with my app, maybe it's something on react-native-web internals, maybe it's react-window, maybe it's chrome, maybe it's the cost of the garbage collector. Needs more investigation, help much appreciated: devhubapp/devhub#155

@brunolemos
Copy link
Contributor Author

brunolemos commented Sep 29, 2019

Closing since I don’t have these issues anymore and they might have been mostly because the renders were a bit expensive.

I’ve rewritten my list item components to be simpler. For example, their height are now pre-calculated (this is important) and they don’t do any computation inside the render. Also removed the deep nested custom components, it’s now a single ItemComponent as flat as possible.

Things are fast now.

I did switch to react-window on web, it is faster than FlatList but not a huge deal. The bigger optimizations were the other things I mentioned.

@nihp
Copy link

nihp commented Oct 24, 2019

@brunolemos Any documentation related to disableVirtualisation for web, android and iOS

@RichardLindhout
Copy link
Contributor

Just use a normal scrollview instead of a flatlist in the web @nihp.

@nihp
Copy link

nihp commented Oct 24, 2019

@RichardLindhout I am using iOS, here I need to know about the disableVirtualisation. I am using SrcollView and Flatlist in my app.

Need to smoothen the scroll in iOS so I asked about disableVirtualisation

@RichardLindhout
Copy link
Contributor

There is no virtualization in the flatlist of react-native-web.

@RichardLindhout
Copy link
Contributor

@nandorojo
Copy link
Contributor

nandorojo commented Nov 2, 2020

I solved this in my app by remaking my renderItem component from scratch. It went from terrible, glitchy performance to butter. I was originally passing entire data objects to each list item. Then, in each component, I was checking my SWR cache to get more data about them.

For context, I was displaying an infinite scroll search list of artists (think Netflix search.) I wanted to show users if they've liked the artist before or not, etc. I was originally looping through a list, passing each list item down to my component, and then grabbing metadata about my list item from within each component. Why not? After all, each component could just access the global state, right? Turns out, this was very expensive.

I changed this by turning each card into a fully "dumb" component, with very little code. It only receives primitive props (hasLiked: boolean, starRating: number) and memoized functions (onPress(id: string)). This helped dramatically.

Oddly, adding getItemLayout caused it to flicker a lot on native. This is pretty surprising, given that I am using deterministic sizes. 🤷‍♂️

I highly recommend making your list item nimble. Don't pass the entire list object down to it – only the data it needs. Keep any functions at the top level of the list component, and memoize them before passing them down to each item. And, as people have mentioned here, memoize each list item if they re-render too often and you're seeing issues.

In case it's useful, these are the props I ended up with. I memoized every function I passed to FlatList (this might be overkill, but it worked for me.)

<FlatList
  data={hits}
  removeClippedSubviews={Platform.OS !== 'web'}
  renderItem={renderItemFast} 
  ListEmptyComponent={ListEmptyComponent} // memoized function
  numColumns={itemsPerRow}
  // force re-render when items per row changes, since this can't be changed on the fly
  key={`list-${itemsPerRow}`} 
  initialNumToRender={10}
  keyExtractor={keyExtractor}
  onEndReached={fetchMore}
  keyboardDismissMode="on-drag"
  keyboardShouldPersistTaps="handled"
  style={style.container} 
/>

@viniciushernandes
Copy link

Only add disableVirtualization and worked for me.

@louicoder
Copy link

Only add disableVirtualization and it worked for me.

This works like a charm. I'm curious to know where there's a deprecated label on it. I have tried to optimize my Flatlists in all ways but I would always get a warning Of Flatlist not optimized. This looks like the best option for now.

Also for those curious, there's another package called recyclerlistview which offers native performance

@JalalMitali
Copy link

@louicoder @viniciushernandes @brunolemos any better solution than disableVirtualization ? thanks in advance!

@Lump01
Copy link

Lump01 commented Mar 15, 2024

I'm creating a messaging feature to an app and was having issues with sorting then updating state with new messages (10 new ones at a time) as the user scrolls through old messages. I would face trouble with this as I got to 40-50 messages.

@nandorojo solution is good advice, but did not work for me because each message component has to calculate whether to show the date above it based on the previous message, pseudo-code is like this:

if (currentMessage.timestamp = today AND previousMessage.timestamp != today) {
  display "Today" above currentMessage;
}

(if current message was sent today AND previous message was not sent today, then show "Today" above current message and show yesterday above previous message)

Instead of sorting THEN updating the whole messages state variable in my FlatList, I resolved this by concatenating the new messages as the user scrolls and making the following change in my data prop on :

<FlatList
  removeClippedSubviews
  // disableVirtualization
  keyExtractor={keyExtractor}
  maxToRenderPerBatch={10}
  initialNumToRender={10}
  data={messages
        .slice()     // <------- add .slice() before .sort()
        .sort((a, b) => new Date(b?.createdAt) - new Date(a?.createdAt))}
  renderItem={renderItem}
  inverted
/>

Array.slice() does the following according to developer.mozilla.org:

The slice() method of Array instances returns a shallow copy of a portion of an array into a new array object selected from start to end (end not included) where start and end represent the index of items in that array. The original array will not be modified.

Updating 150 element long state array is very costly to performance especially when sorting WHILE scrolling. That was my issue. Another solution along the lines of what @nandorojo said is that you could do logic on the backend so it's easy to just display information based on what the front end receives.

Credit to this StackOverflow post and Mozilla's developer docs on this matter.

@arys
Copy link

arys commented Apr 20, 2024

increasing windowSize fixed it for me
windowSize={40}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests