React Native FlatList — A Comprehensive guide — Part 1
Gautham Vijayan | Lead React Native Developer
Introduction
Rendering huge number of elements in the app can slow down performance. In React Native to overcome performance issues, we can utilize FlatList to overcome this problem.
In this post, we will be diving into how we can implement flatlist to properly render a large number of elements without any performance hit. I will also be explaining some neat tricks with some video examples.
This will be a 2 part series on FlatList, and this part will focus on the core parts like the what is the need of using FlatList when we can use map function as well as how we can use RefreshControl to refresh our FlatList elements.
Implementation
This post assumes you have created a react native project and have your project up and running.
To explain the concepts in detail we are going to use dummy data points and the data would be in array of objects format. Initially we will be using 5 elements and will be increasing it to explain how we can improve the performance when user scrolls faster to the bottom of the screen.
We will be looking into a core Flatlist features like Pull to refresh feature which is used by many applications to refresh existing data points. For example, If you want to refresh your feed in X (Twitter), Youtube app, you will pull from the top and a loading animation will be shown and new data will be populated to the screen.
We will also be looking into making the FlatList into a Horizontal list with a single prop change.
Ok lets dive into the code.
First I will be making a card component which will have a nice unsplash image, the title and the author name of the person who took the picture. So the array of values will be in the following format.
const data = [{img: '', title: '', authorName: ''}];
Now we are going to create the card component and add the component in the renderItem prop. The rule of thumb for rendering list of items in React JS is each element should have a unique key in them so that when a re-render occurs the React reconciliation algorithm checks if the key has changed or not and if the keys are not changed the FlatList Items will not get re-rendered.
This is the main reason behind having unique ids/keys for all the elements.
For this we will be using keyExtractor prop to explain the FlatList to use a particular key value for this purpose. If you have “key” property in your data format, FlatList automatically takes that. So you can keep this in mind.
FlatList uses a concept called Virtualization which will only render the elements which are in the Viewport of the user. Viewport is the view which is currently visible for the user and FlatList will optimise the list elements in this manner.
This performance optimization is not available when using map function and may cause issues when huge amount of elements are required to be shown to the user.
Now we can add ListHeaderComponent & ListFooterComponent to the Flatlist to add a neat header & footer to the Flatlist elements.
If the array is empty, we can use ListEmptyComponent prop and create a component to show that the list is empty.
Combining all the concepts above, you can view the code below.
import {
Dimensions,
FlatList,
Pressable,
SafeAreaView,
StyleSheet,
Text,
View,
Image,
} from 'react-native';
import React from 'react';
const screenWidth = Dimensions.get('screen').width;
const CardComponent = ({item}) => {
return (
<Pressable
style={styles.movieItem}
onPress={() => {
navigateToScreen(item);
}}>
<Image
style={styles.poster}
source={{
uri: item.img,
}}
/>
<View style={styles.movieDetails}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.description}>{item.authorName}</Text>
</View>
</Pressable>
);
};
const ListHeaderComponent = () => {
return (
<View style={styles.center}>
<Text style={styles.demoTitle}>FlatList Demo</Text>
</View>
);
};
const ListFooterComponent = () => {
return (
<View style={styles.center}>
<Text style={styles.demoTitle}>FlatList Footer</Text>
</View>
);
};
const ListEmptyComponent = () => {
return (
<View style={styles.center}>
<Text style={styles.demoTitle}>FlatList Empty</Text>
</View>
);
};
const FlatListIntegration = () => {
const data = [
{
title: 'Formentera Ship',
authorName: 'Roberto H',
img: 'https://images.unsplash.com/photo-1542397284385-6010376c5337?q=80&w=3174&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
{
title: 'Sunset Ship',
authorName: 'Bruce Warrington',
img: 'https://images.unsplash.com/photo-1552353617-3bfd679b3bdd?q=80&w=2970&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
{
title: 'Green Boats',
authorName: 'Ian Badenhorst',
img: 'https://images.unsplash.com/photo-1575893240675-17e719ffa7c5?q=80&w=2912&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
{
title: 'Moisture on car',
authorName: 'Simon Goetz',
img: 'https://images.unsplash.com/photo-1498528738175-10068e55f9a7?q=80&w=2001&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
];
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={CardComponent}
keyExtractor={item => item?.title}
showsVerticalScrollIndicator={false}
ListHeaderComponent={ListHeaderComponent}
ListFooterComponent={ListFooterComponent}
ListEmptyComponent={ListEmptyComponent}
/>
</SafeAreaView>
);
};
export default FlatListIntegration;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
center: {
alignItems: 'center',
justifyContent: 'center',
},
movieItem: {
marginVertical: 10,
alignItems: 'center',
backgroundColor: 'white',
marginHorizontal: 10,
borderColor: '#00A9F1',
borderWidth: 1,
borderRadius: 15,
},
poster: {
width: screenWidth * 0.9,
height: 220,
resizeMode: 'cover',
marginVertical: 10,
borderRadius: 10,
},
movieDetails: {
padding: 5,
paddingHorizontal: 7,
alignItems: 'flex-start',
width: screenWidth * 0.85,
},
title: {
fontSize: 20,
marginBottom: 5,
fontFamily: 'Pangram-Bold',
lineHeight: 26,
color: 'black',
},
demoTitle: {
fontSize: 22,
marginBottom: 5,
fontFamily: 'Pangram-Bold',
lineHeight: 26,
marginVertical: 10,
color: 'black',
},
description: {
fontSize: 17,
fontFamily: 'Pangram-Regular',
lineHeight: 26,
marginBottom: 10,
textAlign: 'left',
color: 'black',
},
about: {
fontSize: 17,
fontFamily: 'Pangram-Regular',
lineHeight: 30,
marginVertical: 10,
marginHorizontal: 20,
color: 'black',
},
searchDescription: {
fontSize: 22,
fontFamily: 'Pangram-Regular',
marginBottom: 10,
},
searchLoader: {
paddingTop: 30,
marginTop: 20,
marginBottom: 30,
},
searchMovies: {
width: screenWidth * 0.8,
height: 300,
},
});
We added a prop called showsVerticalScrollIndicator={false} to prevent scrollbars from being shown when the user scrolls to the bottom. The default is true and if you dont include this prop a scrollbar will be shown.
There is a prop called initialNumToRender which the FlatList uses to render on the first render of the FlatList.
We can increase the amount of elements to rendered on the first time by increasing the number. The default number is 10 and we can increase it higher to prevent some edge cases.
Pull To Refresh
So in order to integrate the pull to refresh function in React Native we can use the refreshControl prop of FlatList to get it working. We will use “RefreshControl” from React Native to implement this natively.
The RefreshControl will be using “refreshing” prop and “onRefresh” function to refresh the FlatList component.
We will be using useState to get this working.
When user pulls the FlatList from the top, the refreshControl prop will be triggered and the native RefreshControl Component can be shown with help of the onRefresh function.
const [isRefreshing, setIsRefreshing] = useState(false);
const onRefresh = async () => {
setIsRefreshing(true);
// Refresh data here
setTimeout(() => {
alert('Data refreshed');
setIsRefreshing(false);
}, 2000);
};
<FlatList
data={data}
renderItem={CardComponent}
keyExtractor={item => item?.title}
showsVerticalScrollIndicator={false}
ListHeaderComponent={ListHeaderComponent}
ListFooterComponent={ListFooterComponent}
ListEmptyComponent={ListEmptyComponent}
initialNumToRender={15}
refreshControl={
<RefreshControl refreshing={isRefreshing} onRefresh={onRefresh} />
}
/>
Here is a GIF explaining showcasing the refresh Control feature.
Horizontal List
To convert the existing FlatList to a horizontal scrollable list, we simply have to use the horizontal={true} prop to make it scroll in horizonal.
If you want to remove the horizontal scroll bar, you can use showsHorizontalScrollIndicator={false} to do so.
Complete Code
import {
Dimensions,
FlatList,
Pressable,
SafeAreaView,
StyleSheet,
Text,
View,
Image,
RefreshControl,
} from 'react-native';
import React, {useState} from 'react';
const screenWidth = Dimensions.get('screen').width;
const CardComponent = ({item}) => {
return (
<Pressable
style={styles.movieItem}
onPress={() => {
navigateToScreen(item);
}}>
<Image
style={styles.poster}
source={{
uri: item.img,
}}
/>
<View style={styles.movieDetails}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.description}>{item.authorName}</Text>
</View>
</Pressable>
);
};
const ListHeaderComponent = () => {
return (
<View style={styles.center}>
<Text style={styles.demoTitle}>FlatList Demo</Text>
</View>
);
};
const ListFooterComponent = () => {
return (
<View style={styles.center}>
<Text style={styles.demoTitle}>FlatList Footer</Text>
</View>
);
};
const ListEmptyComponent = () => {
return (
<View style={styles.center}>
<Text style={styles.demoTitle}>FlatList Empty</Text>
</View>
);
};
const FlatListIntegration = () => {
const [isRefreshing, setIsRefreshing] = useState(false);
const onRefresh = async () => {
setIsRefreshing(true);
// Refresh data here
setTimeout(() => {
alert('Data refreshed');
setIsRefreshing(false);
}, 2000);
};
const data = [
{
title: 'Formentera Ship',
authorName: 'Roberto H',
img: 'https://images.unsplash.com/photo-1542397284385-6010376c5337?q=80&w=3174&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
{
title: 'Sunset Ship',
authorName: 'Bruce Warrington',
img: 'https://images.unsplash.com/photo-1552353617-3bfd679b3bdd?q=80&w=2970&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
{
title: 'Green Boats',
authorName: 'Ian Badenhorst',
img: 'https://images.unsplash.com/photo-1575893240675-17e719ffa7c5?q=80&w=2912&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
{
title: 'Moisture on car',
authorName: 'Simon Goetz',
img: 'https://images.unsplash.com/photo-1498528738175-10068e55f9a7?q=80&w=2001&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
];
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={CardComponent}
keyExtractor={item => item?.title}
showsVerticalScrollIndicator={false}
ListHeaderComponent={ListHeaderComponent}
ListFooterComponent={ListFooterComponent}
ListEmptyComponent={ListEmptyComponent}
initialNumToRender={15}
refreshControl={
<RefreshControl refreshing={isRefreshing} onRefresh={onRefresh} />
}
/>
</SafeAreaView>
);
};
export default FlatListIntegration;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
center: {
alignItems: 'center',
justifyContent: 'center',
},
movieItem: {
marginVertical: 10,
alignItems: 'center',
backgroundColor: 'white',
marginHorizontal: 10,
borderColor: '#00A9F1',
borderWidth: 1,
borderRadius: 15,
},
poster: {
width: screenWidth * 0.9,
height: 220,
resizeMode: 'cover',
marginVertical: 10,
borderRadius: 10,
},
movieDetails: {
padding: 5,
paddingHorizontal: 7,
alignItems: 'flex-start',
width: screenWidth * 0.85,
},
title: {
fontSize: 20,
marginBottom: 5,
fontFamily: 'Pangram-Bold',
lineHeight: 26,
color: 'black',
},
demoTitle: {
fontSize: 22,
marginBottom: 5,
fontFamily: 'Pangram-Bold',
lineHeight: 26,
marginVertical: 10,
color: 'black',
},
description: {
fontSize: 17,
fontFamily: 'Pangram-Regular',
lineHeight: 26,
marginBottom: 10,
textAlign: 'left',
color: 'black',
},
about: {
fontSize: 17,
fontFamily: 'Pangram-Regular',
lineHeight: 30,
marginVertical: 10,
marginHorizontal: 20,
color: 'black',
},
searchDescription: {
fontSize: 22,
fontFamily: 'Pangram-Regular',
marginBottom: 10,
},
searchLoader: {
paddingTop: 30,
marginTop: 20,
marginBottom: 30,
},
searchMovies: {
width: screenWidth * 0.8,
height: 300,
},
});
Conclusion
This concludes the first post in the series of FlatList.
In the next post we will discuss about,
- How we can scroll to a particular element in the FlatList.
- Implementing Animated.FlatList.
- Utilising numColumns prop to make multi column layout
And many more concepts.
Let’s meet in the next post.