React-Native FlatList performance problems with large list - javascript

My code gets Json data to an array lists the data using a FlatList. It looks like a phonebook photo and text in a row.
Here is My code:
renderItem = ({ item }) =>
(
<ListItem
title={item.username}
avatar={{ uri: item.photo }}
/>
)
render() {
console.log(this.state.myData);
return (
<View style={styles.container}>
<FlatList
data={this.state.myData}
renderItem={this.renderItem}
/>
</View>
);
}
Its works and I get the output, but the performance is slow. Rendering takes approximately 10 seconds which is annoying to the user. What should I do to make it faster?

Edit Dec 20 '19: The information on this answer became part of the official docs. You should check it out!
Edit May 26 '18: This answer became a bigger and more complete post on github
If you follow this thread, you'll see that react's team is already aware of this performance issue.
There is no silver bullet for this issue, you have to consider the trade offs of every approach and what you think is a good experience for your audience. But fortunately there are several tweaks you can try and improve your FlatList.
Terms and meanings
There are a lot of terms used (on docs or some issues) that were confusing for me at first. So, let's get this out of the way from the start.
VirtualizedList is the component behind FlatList, and is React Native's implementation of the 'virtual list' concept.
Performance, in this context, imply a smooth (not choppy) scroll (and navigation in or out of your list) experience.
Memory consumption, in this context, is how much information about your list is being stored in memory, which could lead to a app crash.
Blank areas means that the VirtualizedList couldn't render your items fast enough, so you enter on a part of your list with non rendered components.
Window here is not your viewport but rather, size of the area in which items should be rendered.
Props
One way to improve your FlatList is by tweaking it's props. Here are a list of props that can help your with that.
removeClippedSubviews
You can set the removeClippedSubviews prop to true, which unmount components that are off of the window.
Win: This is very memory friendly, as you will always have a little rendered list.
Trade offs: Be aware, that this implementation can have bugs, such as missing content if you use it on a component that will not unmount (such as a navigation route).
It also can be less performant, having choppy scroll animations for big lists with complex items on not-so-good devices, as it make crazy amounts of calculations per scroll.
maxToRenderPerBatch
You can set the maxToRenderPerBatch={number}, which is a VirtualizedList prop that can be passed directly to FlatList. With this, you can control the amount of items rendered per batch, which is the next chunk of items rendered on every scroll.
Win: Setting a bigger number means less visual blank areas when scrolling (a better the fill rate).
Trade offs: More items per batch means less JavaScript performance, which means less responsiveness (clicking a item and opening the detail). If you have a static and non-interactive list, this could be the way to go.
initialNumToRender
You can set the initialNumToRender={number}. This means the initial amount of items to render.
Win: You can set this value to the precise number of items that would cover the screen for every device. This can be a big performance boost when rendering the list component.
Trade offs: You are most likely to see blank areas when setting a low initialNumToRender.
windowSize
You can set the windowSize={number}. The number passed here is a measurement unit where 1 is equivalent to your viewport height. The default value is 21, being 10 viewports above, 10 below, and one in between.
Win: If you're worried mainly about performance, you can set a bigger windowSize so your list will run smoothly and with less blank space. If you're mainly worried about memory consumption, you can set a lower windowSize so your rendered list will be smaller.
Trade offs: For a bigger windowSize, you will have a bigger memory consumption. For a lower windowSize, you will have lower performance and bigger change of seeing blank areas.
legacyImplementation
This prop, when true, make your FlatList rely on the older ListView, instead of VirtualizedList.
Win: This will make your list definitely perform better, as it removes virtualization and render all your items at once.
Trade offs: Your memory consumption goes to the roof and chances are good that a big list (100+) with complex items will crash your app.
It also fires a warning that the above tweaks will not work, because you're now on a ListView.
disableVirtualization
You will see people advocation the use of this prop on some issues. But this is deprecated now. This used to do something similar to legacyImplementation.
List items
There are also some win-win strategies that involves your list item components. They are being managed by VirtualizedList a lot, so they need to be fast.
Use simple components
The more complex your components are, the slower they will render. Try to avoid a lot of logic and nesting in your list items. If you are reusing this list item component a lot in your app, create a duplicate just for your big lists and make them with less logic as possible and less nested as possible.
Use light components
The heavier your components are, the slower they render. Avoid heavy images (use a cropped version for list items, as small as possible). Talk to your design team, use as little effects and interactions and information as possible in your list. Save them to your item's detail.
Use shouldComponentUpdate
Implement update verification to your components. React's PureComponent is mostly for when you don't have time to think. If you're reading this, you do have time, so, create the strictest rules for your list item components. If your list is simple enough, you could even use
shouldComponentUpdate() {
return false
}

Here are some improvements you can do to optimize your flatlist:
Pagination
You can paginate your data in your backend and refetch it when your scroll gets closer to the end. onEndReached and onEndReachedThreshold can help you.
Update your React Native version to 49 or latest
Fiber 16 has amazing performance improvement which makes everything runs faster and smoother
Use PureComponent for render item
PureComponent improves the rendering and memory usage of your component, creating render items as pure gives you a better performance experience
Define getItemLayout prop
It improves item rendering since React will previously know its layout definition
Hope it helps

Wrap your <FlatList> within a <ScrollView> to prevent white pages and performance issues on large datasets

Related

What size ScrollView starts to affect performance (React Native)

I'm trying to implement a scroll animation on my FlatList using react-native-reanimated2 but I'm having trouble doing so. However, I am able to implement the animation using a ScrollView. The list is of a fixed size (no more than 20 items). I was just wondering, how many items can the ScrollView render before it starts to cause performance issues (because it renders all items at once). Is there a benchmark value? I'm asking because I don't want to have performance issues in my app because the Scrollview. Thanks.
Why don't you try it? Make a ScrollView with one item, twenty, a hundred, a thousand. Scroll to test, or build in native editors and use their perf monitors. For an accurate representation, use release builds - dev builds will always be slower.
It largely depends on how complex the items are, and the capabilities of the device. Single lines of text on a recent phone? Should be fine with lots. Multiple animations and images per item? More than a couple might choke on an old device.

React Why Unmount Component Slow

I have a component that wraps a bunch of ReactList components.
ReactList component is a component that does infinite scrolling ie only load what's on viewport. It has couple of modes: simple and uniform. I'm using simple which just loads and loads as you scroll down (making the scrollable page longer). Uniform loads and removes the above section that is now out of viewport. I tried using uniform mode, and if it worked I wouldn't even need to ask what I'm about to, but it's super buggy and defeats the purpose. Simple on the other hand is super fast scrolling and loading is equally fast.
I broken down my list into few groups where user can click and it will load that ReactList component. If the scrollable page is short, ie, user hasn't scrolled all the way down, changing between the group of lists is fast enough. ~2s. However, if the page is scrolled all the way down, and trying to change list takes ~6s.
I noticed that the page is fully loaded too, ie nothing more to load. So okay, I think maybe if I change the key for the wrapper parent component and just remount the component, it should be fast right? Nope. The component that reloads is short, only one viewport length, but it takes around ~5s.
I noticed changing out of the fully loaded list component page to just regular single line text "Hello world" component still took around the same amount! ~5s. What's up with that?
QUESTION: Why is unmounting a long page with many small components in a list take so long to just unmount?
Sorry I realize this is long and full of text but I thought I should explain my situation.
As it is an old question, I really want to put it to an end.
Quick answer: use virtual list, something like uniform mode you described, but better maintained with better api. You can get bunch of packages if you search react virtual list, use ones with more stars and better maintained, and most of all fit your case.
Surely you got lots of small components when you scroll. In react it's not like removing a node is one shot thing, it needs to unmount every leaf from the tree, and recursively unmount leaves untuil reach that node. So if you got a lot of components, even today's new react won't help you out.
With virtual list, you get less components although your user doesn't know, so it would be quick.

React-like programming without React

I grew up using JQuery and have been following a programming pattern which one could say is "React-like", but not using React. I would like to know how my graphics performance is doing so well, nonetheless.
As an example, in my front-end, I have a table that displays some "state" (in React terms). However, this "state" for me is just kept in global variables. I have an update_table() function which is the central place where updates to the table happen. It takes the "state" and renders the table with it. The first thing it does is call $("#table").empty() to get a clean start and then fills in the rows with the "state" information.
I have some dynamically changing data (the "state") every 2-3 seconds on the server side which I poll using Ajax and once I get the data/"state", I just call update_table().
This is the perfect problem for solving with React, I know. However, after implementing this simple solution with JQuery, I see that it works just fine (I'm not populating a huge table here; I have a max of 20 rows and 5 columns).
I expected to see flickering because of the $("#table").empty() call followed by adding rows one-by-one inside the update_table() function. However, the browser (chrome/safari) somehow seems to be doing a very good job of updating only that elements that have actually changed (Almost as if the browser has an implementation of Virtual DOM/diffing, like React!)
I guess your question is why you can have such a good graphics performance without React.
What you see as a "good graphics performance" really is a matter of definition or, worse, opinion.
The classic Netscape processing cycle (which all modern browsers inherit) has basically four main stages. Here is the full-blown Gecko engine description.
As long as you manipulate the DOM, you're in the "DOM update" stage and no rendering is performed AT ALL. Only when your code yields, the next stage starts. Because of the DOM changes the sizes or positions of some elements may have changed, too. So this stage recomputes the layout. After this stage, the next is rendering, where the pixels are redrawn.
This means that if your code changes a very large number elements in the DOM, they are all still rendered together, and not in an incremental fashion. So, the empty() call does not render if you repopulate the table immediately after.
Now, when you see the pixels of an element like "13872", the rendering stage may render those at the exact same position with the exact same colors. You don't have any change in pixel color, and thus there is no flickering you could see.
That said, your graphics performance is excellent -- yes. But how did you measure it? You just looked at it and decided that it's perfect. Now, visually it really may be very very good. Because all you need is avoid the layout stage from sizing/positioning something differently.
But actual performance is not measured with the lazy eyes of us humans (there are many usability studies in that field, let's say that one frame at 60 Hz takes 16.6 ms, so it is enough to render in less than that). It is measured with an actual metric (updates per second or whatever). Consider that on older machines with older browsers and slower graphics cards your "excellent" performance may look shameful. How do you know it is still good on an old Toshiba tablet with 64 MB graphics memory?
And what about scaling? If you have 100x the elements you have now, are you sure it will scale well? What if some data takes more (or less) space and changes the whole layout? All of these edge conditions may not be covered by your simple approach.
A library like React takes into account those cases you may not have encountered yet, and offers a uniform pattern to approach them.
So if you are happy with your solution you don't need React. I often avoid jQuery because ES5/ES6 is already pretty good these days and I can just jot down 3-4 lines of code using document.getElementById() and such. But I realize that on larger projects or complex cases jQuery is the perfect tool.
Look at React like that: a tool that is useful when you realize you need it, and cumbersome when you think you can do without. It's all up to you :)
When you have something like this:
$("#table").empty()
.html("... new content of the table ... ");
then the following happens:
.empty() removes content and marks rendering tree / layout as invalid.
.html() adds new content and marks rendering tree / layout as invalid.
mark as invalid among other things calls InvalidateRect() (on Windows) that causes the window to receive WM_PAINT event at some point in future.
By handling WM_PAINT the browser will calculate layout and render all the result.
Therefore multiple change requests will be collapsed into single window painting operation.

Better to load javascript navigation hierarchy all at once or level by level?

I am working on some custom jQuery/javascript navigation for a site and I am curious about the performance implications of a design decision.
The way it works is for every option there are up to 8 child options, this hierarchy can go 4 levels deep. I believe this makes for 8^4 or 4096 possible navigation items (probably less but this is the max). These relationships are defined on the server side.
Currently I am working with test data, so there are only about 50 navigation items. When the page loads, I create every navigation item and then only display what is needed for the current selection.
Should I consider rewriting this to only load the items that are needed when a selection is made via an AJAX call or something? I am concerned that my current approach may not scale well if it goes up to 4096 navigation items.
If having 4096 navigation items is a real possibility then you'll have to do something like what you're describing. Simply loading the items into the DOM will take considerable time and further processing will cause greater delays and a poor experience.
For a small number of items, it probably isn't worth your while to over-engineer the solution. However, the performance gains on a large number of items would be expected to be significant.
Here is an example of on-demand loading in a Telerik Treeview. I'm not advocating purchasing the controls (great controls but expensive) however it is an excellent example of what is possible. Coding this on your own wouldn't be difficult to do and, as you can see, makes for a great user experience.
My two cents: if you have the time, do it now before things get even more complicated/difficult to do later.
Downloading them all at the same time is definitely an option, though loading them into the DOM is another story. If you really reach the 4096 possibility limit, you can be looking at pushing down 1-2 megabytes a page load (not to much considering image sizes). Unless you are looking at more data (maybe 16 nodes, 8 levels deep 16^8), then it would be a valid concern.
you could always load 2 deep (8^2 = 64), then when they open a panel, load everything for that panel. The second layer they need to click through should give you enough time to load the rest of the values.

I have a couple thousand javascript objects which I need to display and scroll through. What are my options?

I'm working off of designs which show a scrollable box containing a list of a user's "contacts".
Users may have up to 10,000 contacts.
For now assume that all contacts are already in memory, and I'm simply trying to draw them. If you want to comment on the question of how wise it is to load 10k items of data in a browser, please do it here.
There are two techniques I've seen for managing a huge list like this inside a scrollable box.
Just Load Them All
This seems to be how gmail approaches displaying contacts. I currently have 2k contacts in gmail. If I click "all contacts", I get a short delay, then the scrollable box at the right begins to fill with contacts. It looks like they're breaking the task into chunks, probably separating the DOM additions into smaller steps and putting those steps into timeouts in order to not freeze the entire interface while the process completes.
pros:
Simple to implement
Uses native UI elements the way they were designed to be used
Google does it, it must be ok
cons
Not totally snappy -- there is some delay involved, even on my development machine running Firefox. There will probably be quite a lot of delay for a user running a slower machine running IE6
I don't know what sort of limits there are in how large I can allow the DOM to grow, but it seems to me there must be some limit to how many nodes I can add to it. How will an older browser on an older machine react if I ask it to hold 10k nodes in the DOM?
Draw As Needed
This seems to be how Yahoo deals with displaying contact lists. The create a scrollable box, put a super-tall, empty placeholder inside it, and draw contacts only when the user scrolls to reveal them.
pros:
DOM nodes are drawn only as needed, so there's very little delay while loading, and much less risk of overloading the browser with too many DOM nodes
cons:
Trickier to implement, and more opportunity for bugs. For example, if I scroll quickly in the yahoo mail contact manager as soon as the page loads, I'm able to get contacts to load on top of one another. Of course, bugs can be worked out, but obviously this approach will introduce more bugs.
There's still the potential to add a huge number of DOM nodes. If the user scrolls slowly through the entire list, every item will get drawn, and we'll still end up with an enormous DOM
Are there other approaches in common use for displaying a huge list? Any more pros or cons with each approach to add? Any experience/problems/success using either of these approaches?
I would chunk up the DOM-writing into handle-able amounts (say, 25 or 50), then draw the chunks on demand. I wouldn't worry about removing the old DOM elements until the amount drawn gets quite large.
I would divide the contacts into chunks, and keep a sort of view buffer alive that changes which chunks are written to the DOM as the user scrolls through the list. That way the total number of dom elements never rises above a certain threshold. Could be fairly tricky to implement, however.
Using this method you can also dynamically modify the size of chunks and the size of the buffer, depending on the browser's performance (dynamic performance optimization), which could help out quite a bit.
It's definitely non-trivial to implement, however.
The bug you see in Yahoo may be due to absolutely positioned elements: if you keep your CSS simple and avoid absolutely/relatively positioning your contact entries, it shouldn't happen.

Categories

Resources