Migrating React Native ListView to new FlatList – Infinite Scroll

By w s / August 9, 2017
react native infinite scroll

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:

  1. FlatList
  2. 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:

  1. dataSource to data – As opposed to ListView that requires a datasource of type ListView.DataSource, FlatList simply requires an array of item.
  2. 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.
  3. 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.
  4. 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.

export default class Application extends Component {
...
render() {
if (this.state.isLoading) {
...
} else {
return (
<FlatList
...
ListFooterComponent={() => { // replaces renderFooter={() => {
return (
this.state.isLoadingMore &&
<View style={{ flex: 1, padding: 10 }}>
<ActivityIndicator size="small" />
</View>
);
}}
...
/>
);
}
}
}

 

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!