Lists are one of the common scrollable components to display similar types of data objects. A list is like an enhanced version of a ScrollView
component to display data.
React Native provides a FlatList
component to create a list. FlatList
only renders the list items that can be displayed on the screen. Additionally, FlatList
offers many inbuilt features like vertical/horizontal scrolling, header/footer views, separator, pull to refresh, lazy loading, etc.
This guide will explain the important details to create and optimize a list of cats images using the TheCatsAPI in React Native.
FlatList is a specialized implementation of the VirtualizedList
component to display a limited number of items that can fit inside the current window. The rest of the items will be rendered with the list scrolling action. FlatList can simply be implemented using the data
and renderItem
props to create a list. There are many other optional props available to add more features, so the props can be categorized into primary and optional.
data
takes an array of items, of type any
, to populate items in the list.renderItem
requires a function that takes an item object as an input from the data
source to construct and return a list-item component.Optional props are used to decorate FlatList using an item-divider, header/footer, pull-to-refresh, handle refreshing or optimization logic:
ItemSeparatorComponent
is used to add a separator to visually separate items.keyExtractor
is used to provide a unique value (ID, email, etc.) to avoid the recreation of the list by tracking the reordering of the items.extraData
takes a boolean
value to re-render the current list. FlatList is a PureComponent
that does not re-render against any change in the state
or props
objects, so extraData
prop is used to re-render the current list if the data
array is being mutated.initialNumToRender
is used to render a minimum number of item-components in a FlatList for efficiency.ListEmptyComponent
is used to display an empty view while the data is being downloaded.ListHeaderComponent
is used to add a header component like search, menu-items, etc.ListFooterComponent
is used to add a footer component like total items, data summary etc.getItemLayout
returns the predefined size of the list-item component to skip the size calculation process at runtime to speed up the rendering process.horizontal
prop takes a boolean
value to create a horizontal list by returning like horizontal={true}
.numColumns
is used to create a column-based list. onRefresh
and refreshing
are used to implement pull-to-refresh controls, and maintain their visibility using a boolean
flag.onEndReached
and onEndReachedThreshold
are used to implement lazy loading callback with a given threshold value.
There are other props that can be used to implement style, scrolling, etc.In order to fetch a mock response, use the search
request API of TheCatsAPI, which doesn't require any registration or API key. The network request can simply be implemented using a fetch
request:
1fetchCats() {
2 fetch('https://api.thecatapi.com/v1/images/search?limit=10&page=1') // 1
3 .then(res => res.json()) // 2
4 .then(resJson => {
5 this.setState({ data: resJson }); // 3
6 }).catch(e => console.log(e));
7}
The above request will:
limit
of ten images from page
one.state
object as a value of the data
key by using the setState
function.The fetch
request will provide the data to one of the primary props of the FlatList
. Now implement a function to return a list-item component for the renderItem
prop:
1renderItemComponent = (itemData) => // 1
2 <TouchableOpacity> // 2
3 <Image style={styles.image} source={{ uri: itemData.item.url }} /> // 3
4 </TouchableOpacity>
The key field in the response is the url
that will be used to download and display the image. The following steps explain the working of the renderItemComponent
function:
TouchableOpacity
component is used to implement a click listener because the Image
component does not have an onPress
prop.Image
component takes a URI as a source data to display the image. It will download the image automatically.Import the FlatList
component from the react-native
module to create a list using data
and renderItemComponent
:
1render() {
2 return (
3 <SafeAreaView>
4 <FlatList
5 data={this.state.data}
6 renderItem={item => this.renderItemComponent(item)}
7 />
8 </SafeAreaView>)
9}
The this.state.data
will be updated by the fetch
request, and it will trigger a re-rendering process in the FlatList
component. The above implementation works fine, but the next steps will add more features to improve the FlatList
implementation.
React always uses a unique key to track the updates in a component. By default, the FlatList
looks either for a custom keyExtractor
implementation or a field named key
in a data item, otherwise it uses the array index as the value of key
. The id
is a unique value in the response that can be used to implement the keyExtractor
:
1render() {
2 return (
3 <SafeAreaView>
4 <FlatList
5 data={this.state.data}
6 renderItem={item => this.renderItemComponent(item)}
7 keyExtractor={item => item.id.toString()}
8 />
9 </SafeAreaView>)
10}
A separator is a component that is placed between the list-items. It can be implemented by using a View
component:
1ItemSeprator = () => <View style={{
2 height: 2,
3 width: "100%",
4 backgroundColor: "rgba(0,0,0,0.5)",
5}} />
6
7<FlatList
8 //...
9 ItemSeparatorComponent={this.ItemSeprator}
10/>
Just like
ItemSeparatorComponent
, different components can be implemented forListFooterComponent
andListHeaderComponent
props.
The pull-to-refresh implementation requires a boolean
flag for the refreshing
prop to hide/display the loading indication. The handleRefresh
is a function for the onRefresh
prop to update the data and loading indicator:
1handleRefresh = () => {
2 this.setState({ refreshing: false }, ()=>{this.fetchCats()});
3}
4<FlatList
5 //...
6 refreshing={this.state.refreshing}
7 onRefresh={this.handleRefresh}
8/>)
The value of refreshing
is set to true
before the fetch
call to display the loading indicator. The value of the refreshing
field will be set to false
in case of a successful response or error.
Since FlatList
is a PureComponent
, it assumes that the data is an immutable object and that any change in data is done by setting a new data source object in the state object. Alternatively, use the extraData={this.state.isChanged}
prop to re-render a FlatList
component by updating the value of isChanged
property.
Here's the complete code of the App.js
component:
1/**
2 * @author Pavneet Singh
3 */
4
5import React from "react";
6import {
7 StyleSheet,
8 SafeAreaView,
9 FlatList,
10 View,
11 Image,
12 TouchableOpacity
13} from "react-native";
14
15export default class App extends React.Component {
16 constructor(props) {
17 super(props);
18 this.state = {
19 data: [],
20 refreshing: true,
21 }
22 }
23
24 componentDidMount() {
25 this.fetchCats();
26 }
27
28 fetchCats() {
29 this.setState({ refreshing: true });
30 fetch('https://api.thecatapi.com/v1/images/search?limit=10&page=1')
31 .then(res => res.json())
32 .then(resJson => {
33 this.setState({ data: resJson });
34 this.setState({ refreshing: false });
35 }).catch(e => console.log(e));
36 }
37
38 renderItemComponent = (data) =>
39 <TouchableOpacity style={styles.container}>
40 <Image style={styles.image} source={{ uri: data.item.url }} />
41 </TouchableOpacity>
42
43 ItemSeparator = () => <View style={{
44 height: 2,
45 backgroundColor: "rgba(0,0,0,0.5)",
46 marginLeft: 10,
47 marginRight: 10,
48 }}
49 />
50
51 handleRefresh = () => {
52 this.setState({ refreshing: false }, () => { this.fetchCats() }); // call fetchCats after setting the state
53 }
54
55 render() {
56 return (
57 <SafeAreaView>
58 <FlatList
59 data={this.state.data}
60 renderItem={item => this.renderItemComponent(item)}
61 keyExtractor={item => item.id.toString()}
62 ItemSeparatorComponent={this.ItemSeparator}
63 refreshing={this.state.refreshing}
64 onRefresh={this.handleRefresh}
65 />
66 </SafeAreaView>)
67 }
68}
69
70const styles = StyleSheet.create({
71 container: {
72 height: 300,
73 margin: 10,
74 backgroundColor: '#FFF',
75 borderRadius: 6,
76 },
77 image: {
78 height: '100%',
79 borderRadius: 4,
80 },
81});
initialNumToRender
prop to render the defined numbers of items initially, and maxToRenderPerBatch
to render items for every scroll to avoid blank space on the screen during scrolling. The default value for both of the props is 10
.getItemLayout
prop if the height or width of list-item is fixed. For example, if the height for item is 100 then use:1 getItemLayout={(data, index) => (
2 {length: 100, offset: 100 * index, index}
3 )}
The length
used to define item height (vertically) and offset
is used to define the starting point for the current item index. For example, the starting point of the third item will be 100 * 2 = 200
(array index starts from 0).
FlatList
offers great flexibility to create and configure a list in a React Native app. FlatList
can be optimized for static and dynamic data using various layout calculations and rendering props. The React Native team is working to further improve its performance for large datasets. The optimized project is available on my RNList repository. Happy coding!