React Native — Ultimate Guide on Virtualization Performance Optimization
What is Virtualization?
In this guide, we will discuss list virtualization (also known as windowing). This is the idea of rendering only visible rows of content in a dynamic list instead of the entire list. The rows rendered are only a small subset of the full list with what is visible (the window) moving as the user scrolls. This can improve rendering performance.
Virtualization massively improves memory consumption and performance of large lists by maintaining a finite render window of active items and replacing all items outside of the render window with appropriately sized blank space.
Virtualization Components in React Native
Mainly, these are virtualization components provided by the React Native team.
We will see each of these implementations in code and then do performance optimization on each of them so that they can all load more than 10,000 items.
Native RCTScrollView Component
Before starting with the React Native virtualization component, let’s understand the Native Wrapper of all virtualization components in React Native.
“RCTScrollView” is a native component that is used by the ScrollView component in React Native. It is a wrapper around the native UIScrollView on iOS and the native android.widget.ScrollView on Android. It provides the functionality of scrolling a view that contains multiple subviews or a large content that does not fit the screen. It also supports features such as bouncing, paging, horizontal mode, sticky headers, and scroll indicators.
The relation between “RCTScrollView” and “ScrollView” is that “ScrollView” is a React Native component that renders a “RCTScrollView” under the hood. You can use the “ScrollView” component in your React Native code to render a scrollable view with your custom content. You do not need to import or use the “RCTScrollView” component directly, as it is handled internally by the “ScrollView” component.
The relation between “RCTScrollView” and other list components such as FlatList, SectionList, and RecyclerListView is that they are all based on the same native component (“RCTScrollView”) that provides the scrolling functionality for the list views. However, they have different implementations and features that make them suitable for different use cases and scenarios.
VirtualizedList
Let’s deep dive into the implementation of VirtualizedList. Here is the basic implementation for VirtualizedList.
And below is the function ‘renderItem’ to render the JSX elements for VirtualizedList.
Output (IOS + Android)
Optimize the performance of VirtualizedList
Let’s add some new props to the VirtualizedList component to improve the performance so that it can render a list of 10000+ items.
Understand the props of VirtualizedList
Let’s understand some props that you can pass to the VirtualizedList component to improve the rendering performance.
Some challenges I faced while implementing VirtualizedList
Since VirtualizedList is very customizable and the base implementation for virtualization in React Native, you need to handle some basic needs in your own implementation logic.
#Challange_1
I found that creating multiple columns in VirtualizedList is quite complex and tough to handle. I created a 2-column view by using the code below. Therefore, I needed to add the prop getItem like below.
Here is the code for the function that implements the logic for showing 2 columns.
Now that you have added the getItem prop with the 2-column logic, you need to change the child component behavior for rendering the 2-column view. Here is the code for rendering the 2-column view for the VirtualizedList component.
So, in the child component of VirtualizedList, we see that we needed to add an extra map function. Therefore, every time this item array length will be 2 as I created a 2-column based logic in the getItem prop function. This was a huge change in the code for showing a 2 or multi-column view in the VirtualizedList component.
#Challenge_2:
Another challenge I faced to use getItemLayout. From the previous of my explanation on getItemLayout you already understand that how it works. Basically you need to set length, offset, index.
Where,
So, to set it, I applied this code but I observed that it was not set properly and it made my VirtualizedList view kind of shaky and inappropriate in behavior.
And this is how I set getItemLayout in VirtualizedList.
I skipped getItemLayout in my VirtualizedList implementation as already my VirtualizedList implementation works smoothly with 1000+ image Items.
Final code of VirtualizedList Implementation
Here is the final code of VirtualizedList implementation with both
// Virtualized List Render JSX element ??
function renderVirtualizedListItem({ item, index }) {
return (
<View key={index} style={styles.virtualizedListStyle}>
{item.map((elem, i) => (
<View key={i}>
<Image
style={styles.image}
resizeMethod="resize"
source={{
uri: elem.easyImageUrl
}}
/>
<Text>{elem.nftName}</Text>
</View>
))}
</View>
)
}
// Define how ROW of items will be visible ??
function getTwoColumnVirtualizedListItems(data, index) {
// Return two items per row
let items = []
for (let i = 0; i < 2; i++) {
const item = data[index * 2 + i]
item && items.push(item)
}
return items
}
return (
{/* VirtualizedList ?? ?? ?? */}
<VirtualizedList
data={myNftsData.findMyNfts}
renderItem={renderVirtualizedListItem}
getItemCount={(data) => data.length}
getItem={getTwoColumnVirtualizedListItems}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={11}
updateCellsBatchingPeriod={100}
/>
)
VirtualizedList Final Output (IOS + Android)
FlatList
Let’s implement the above same data for FlatList
Red marked props were used for performance optimization. These are all the same as the props of VirtualizedList. I have already details explained about all these options in VirtualizedList section.
Now, below is the code for renderItem props function implementation for rendering JSX code.
Final code for FlatList
function renderFlatlistItem({ item }) {
return (
<View
style={styles.nftCard}>
<Image
style={styles.image}
resizeMethod="resize"
source={{
uri: item.url
}}
/>
<Text>{item.nftName}</Text>
</View>
)
}
return (
{/* FlatList ?? ?? ?? */}
<FlatList
data={myNftsData.findMyNfts}
renderItem={renderFlatlistItem}
keyExtractor={(item) => item._id}
numColumns={2}
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={100}
initialNumToRender={10}
windowSize={11}
/>
)
领英推荐
Comparative analysis between VirtualizedList & FlatList
There is not much difference in performance between VirtualizedList and FlatList, as they are both based on the same base implementation. However, some factors that may affect the performance are:
In general, FlatList is recommended for rendering basic, flat lists with simple data and layout. VirtualizedListis recommended for rendering complex lists with custom data and layout.
SectionList
Let’s implement the above same data with SectionList. As, now we are going to show some section wise List, so we will add 2 sections in our List.
Let’s see the code of SectionList component
Now let’s see each function props of SectionList
View of SectionList
MultiColumn view in SectionList
Let’s see now how we can make multiple column in SectionList. Basically there are 2 ways in how we can apply multi column view in SectionList.
#First_Way
? Apply FlatList inside SectionList renderItem props function.
Let’s see the code of first way.
Now add the function renderSection into your SectionList component like below
The demerits of this way are:
#Second_Way
? The second way is a bit more complex than the previous way. It is like an algorithmic way to maintain the performance of SectionList as good as the original performance.
You just need to change the renderItem props function like below:
This above function is for the renderItem props functions. Just replace your necessary code in the above function & send the function as renderItem props function of SectionList.
Multi Column view of SectionList
Final code of SectionList
Let me show you the code of SectionList with multi-column and performance optimization:
function sectionsData() {
return [
{
title: 'Male Players',
data: myNftsData.findMyNfts.filter(
(item) => item.collectionType == 'Male'
)
},
{
title: 'Female Players',
data: myNftsData.findMyNfts.filter(
(item) => item.collectionType == 'Female'
)
}
]
}
function renderItem({ section, index }) {
const numColumns = 2 // Show 2 columns
// Return null as ITEM already PUSHED into "items" array
if (index % numColumns !== 0) return null
// "numColumns" row wise ITEMS will be inserted into this Array
const items = []
// Loop for "numColumns" times for each valid "index"
for (let i = index; i < index + numColumns; i++) {
if (i >= section.data.length) {
break
}
// PUSH row wise elements into "items" array. As i set "numColumns" as 2 so the final view ??
// [Item index: 0, Item index: 1]
// [Item index: 2, Item index: 3]
// [Item index: 4, Item index: 5]
items.push(
<View style={styles.nftCard}>
<Image
style={styles.image}
resizeMethod="resize"
source={{
uri: section.data[i].url
}}
/>
<Text>{section.data[i].nftName}</Text>
</View>
)
}
// When index will be 0 it will return ?? (<View> [Item index: 0, Item index: 1] </View>)
// When index will be 2 it will return ?? (<View> [Item index: 2, Item index: 3] </View>)
// When index will be 4 it will return ?? (<View> [Item index: 4, Item index: 5] </View>)
return (
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between'
}}>
{items}
</View>
)
}
function renderSectionHeader({ section }) {
return (
<View style={{ backgroundColor: '#eee', padding: 10 }}>
<Text>{section.title}</Text>
</View>
)
}
return (
<SectionList
sections={sectionsData()}
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
keyExtractor={(item) => item._id}
// Performance props
maxToRenderPerBatch={20} // Increase the number of items per batch
updateCellsBatchingPeriod={100} // Increase the time interval between updates
windowSize={11} // Reduce the number of screens to render
removeClippedSubviews={true} // Remove offscreen views
/>
)
ScrollView
Component that wraps platform ScrollView while providing integration with touch locking “responder” system.
Keep in mind that ScrollViews must have a bounded height in order to work, since they contain unbounded-height children into a bounded container (via a scroll interaction). In order to bound the height of a ScrollView, either set the height of the view directly (discouraged) or make sure all parent views have bounded height. Forgetting to transfer {flex: 1} down the view stack can lead to errors here, which the element inspector makes quick to debug.
Doesn’t yet support other contained responders from blocking this scroll view from becoming the responder.
Load 1000+ images with ScrollView
ScrollView is not for rendering lists. ScrollView is only for rendering views that are scrollable. However, let’s see how we can use ScrollView to render a list.
You can see that I applied “removeClippedSubviews” as true. It will help to render a list. Let’s see what this “removeClippedSubviews” do in ScrollView.
“removeClippedSubviews”: It is still experimental. When true, offscreen child views (whose overflow value is hidden) are removed from their native backing superview when offscreen. This can improve scrolling performance on long lists.
Final code for ScrollView
<ScrollView
removeClippedSubviews={true}
showsVerticalScrollIndicator={false}>
<View key={nftCollectionType}>
{myNftsData.findMyNfts.map((item, index) => (
<View key={index}>
<Image
style={styles.image}
resizeMethod="resize"
source={{
uri: item.url
}}
/>
<Text>{item.nftName}</Text>
</View>
))}
</View>
</ScrollView>
Observation
For my case, I was able to load more than 200 images easily by using it, but I suggest that if you need to render a list, then use any list-supported component like FlatList, VirtualizedList, etc.
ScrollView vs Virtualization
Let’s see some comparative analysis:
When to use ScrollView & When to use FlatList/ VirtualizedList?
ScrollView and FlatList are two components that can render a list of items in React Native. However, they have different ways of handling state. ScrollView maintains the state of its child components, which means that it remembers the data and UI changes of each item in the list. FlatList unmounts and recreates components from scratch when they scroll in and out of view, which means that it does not preserve the data and UI changes of each item in the list.
To illustrate this difference, let’s look at an example. Suppose you have a list of 100 items that can be selected by tapping on them. Each item has a text and a checkbox to indicate its selection status. You can use either ScrollView or FlatList to render this list, but the result will be different.
If you use ScrollView, you will be able to select and deselect any item in the list, and the checkbox will reflect the selection status correctly. This is because ScrollView keeps track of the state of each item and renders them accordingly.
If you use FlatList, you will be able to select and deselect any item in the list, but the checkbox may not reflect the selection status correctly. This is because FlatList does not keep track of the state of each item and renders them from scratch when they scroll in and out of view. For example, if you select an item at the top of the list and then scroll down to the bottom, the item may appear as unselected when you scroll back up. This is because FlatList has unmounted and recreated the item component without preserving its state.
Therefore, if you need to preserve the state of each item in a large list, ScrollView may be more suitable than FlatList. However, ScrollView has other drawbacks such as slow rendering and increased memory usage, so you need to weigh the pros and cons carefully.
Nested Virtualization Error
If you see this error anytime in your metro ??
Error: ? VirtualizedLists should never be nested inside plain ScrollViews ?
Then here is the solution article with error reason & analysis: React Native — Nested Virtualization Anti-pattern (Performance Optimization)