I’ve written how to create an infinite scroll using ListView before. However, redditor reminded me that there are new interfaces to deal with list view when it comes to react native.
React native team have simplified wrangling with list component through the introduction of 2 new classes:
- FlatList
- SectionList
These new list types extend from VirtualizedList which will render a finite amount of items at any given point of time.
This translates to smaller memory footprint and improved performance of massive list.
This article is meant to be a guide on migrating the previously written ListView to FlatList in react native.
New convention with FlatList
In order to move from ListView to FlatList, we have to change some attributes that handles the data source and rendering in ListView.
Attributes that we should update are:
- dataSource to data – As opposed to ListView that requires a datasource of type
ListView.DataSource
, FlatList simply requires an array of item. - renderRow to renderItem – In the new FlatList, instruction to render individual item should be provided in renderItem. On top of individual data, renderItem would also pass in the index and separator.
- Adding keyExtractor – Additional attribute that has to be specified in FlatList that gives the component an instruction on how to define the key for each element.
- Replace renderFooter with ListFooterComponent – ListFooterComponent is the new attribute in FlatList to replace the old renderFooter
Let’s get our migration under way!
Pull in FlatList
First thing first, let’s replace our ListView with FlatList from react-native package.
Simply update the import and the element created
import React, {Component} from "react"; | |
import { | |
Image, | |
StyleSheet, | |
Text, | |
View, | |
FlatList, //Replace ListView with FlatList | |
ActivityIndicator | |
} from "react-native"; | |
export default class Application extends Component { | |
... | |
render() { | |
if (this.state.isLoading) { | |
return ( | |
<View style={styles.container}> | |
<ActivityIndicator size="large"/> | |
</View> | |
); | |
} else { | |
return ( | |
<FlatList | |
... | |
/> | |
); | |
} | |
} | |
} |
Migrating dataSource to data
In the previous ListView, we need to create ListView.DataSource
and then trigger a cloneWithRows
once the data is returned.
With FlatList, we can remove the creation of ListView.DataSource
and completely replace is with pure data array.
First, remove the following codes
export default class Application extends Component { | |
constructor(props) { | |
... | |
this.state = { | |
dataSource: null, // remove this dataSource | |
isLoading: true, | |
isLoadingMore: false, | |
_data: null, | |
_dataAfter: "" | |
}; | |
} | |
... | |
_fetchMore() { | |
this.fetchData(responseJson => { | |
const data = this.state._data.concat(responseJson.data.children); | |
this.setState({ | |
dataSource: this.state.dataSource.cloneWithRows(data), //Remove this cloneWithRows | |
isLoadingMore: false, | |
_data: data, | |
_dataAfter: responseJson.data.after | |
}); | |
}); | |
} | |
componentDidMount() { | |
this.fetchData(responseJson => { | |
//Remove the creation of ListView.DataSource instance | |
//let ds = new ListView.DataSource({ | |
// rowHasChanged: (r1, r2) => r1 !== r2 | |
//}); | |
const data = responseJson.data.children; | |
this.setState({ | |
dataSource: ds.cloneWithRows(data), //remove this dataSource reference | |
isLoading: false, | |
_data: data, | |
_dataAfter: responseJson.data.after | |
}); | |
}); | |
} | |
render() { | |
if (this.state.isLoading) { | |
... | |
} else { | |
return ( | |
<FlatList | |
dataSource={this.state.dataSource} //Remove this reference to dataSource | |
renderRow={rowData => { | |
... | |
}} | |
... | |
/> | |
); | |
} | |
} | |
} |
Next, replace the update to dataSource and dataSource attributes to data like so:
export default class Application extends Component { | |
... | |
render() { | |
if (this.state.isLoading) { | |
... | |
} else { | |
return ( | |
<FlatList | |
data={this.state._data} //Replacing "dataSource={this.state.dataSource}" | |
... | |
/> | |
); | |
} | |
} | |
} |
Migrating renderRow to renderItem
Next item in our agenda is to replace renderRow with renderItem.
This is a relatively simple step. All we need to do is instead of reading rowData, we just need to extract rowData.item
from the object passed into our renderItem function.
1st way, manually extract
export default class Application extends Component { | |
... | |
render() { | |
if (this.state.isLoading) { | |
... | |
} else { | |
return ( | |
<FlatList | |
data={this.state._data} | |
renderItem={rowParameter => { //Replaces renderRow={rowData => { | |
const rowData = rowParameter.item; | |
return ( | |
... | |
); | |
}} | |
... | |
/> | |
); | |
} | |
} | |
} |
2nd way, destructuring parameter
export default class Application extends Component { | |
... | |
render() { | |
if (this.state.isLoading) { | |
... | |
} else { | |
return ( | |
<FlatList | |
data={this.state._data} | |
renderItem={({item: rowData}) => { //Replaces renderRow={rowData => { | |
return ( | |
... | |
); | |
}} | |
... | |
/> | |
); | |
} | |
} | |
} |
Replace renderFooter with ListFooterComponent
To render footer, it’s as simple as renaming the attribute renderFooter with ListFooterComponent.
Adding KeyExtractor
KeyExtractor is the last additional attribute that we need to add into our brand new FlatList.
Since our data structure is Reddit’s subreddit item, we can be sure that each name in the data will be different. All we need to do is to return item.data.name
as the key for our item.
export default class Application extends Component { | |
... | |
render() { | |
if (this.state.isLoading) { | |
... | |
} else { | |
return ( | |
<FlatList | |
... | |
keyExtractor={(item, index) => index} | |
... | |
/> | |
); | |
} | |
} | |
} |
Et Voilà, that’s it, we do not even need to touch the mechanism to render extra item when scroll reaches the end as the API remains the same!
Here’s the completed migration in expo:
Go ahead and run the new inifinite scroll FlatList in expo, your emulator or mobile device!