If you are using a FlatList in React Native, there’s a chance you will want to use the scrollToIndex function to scroll to a certain item’s index. Unfortunately, this function is a lot easier to use with fixed item heights where it is easy to implement the required getItemLayout function. For lists where the row size is dynamic and not easy to compute, we need to respond to the failure case and recursively attempt to scroll to the location.
Case 1: scrollToIndex with Fixed Item Size
import { StatusBar } from 'expo-status-bar';
import Constants from 'expo-constants';
import React from 'react';
import { StyleSheet, Text, View, FlatList } from 'react-native';
export default class App extends React.Component {
constructor(props) {
super(props);
this.flatListRef = null;
}
componentDidMount() {
setTimeout(() => this.flatListRef.scrollToIndex({ index: 4 }), 3000);
}
renderItem({ item }) {
return (
<View style={styles.listItem}>
<Text>{item.data}</Text>
</View>
);
}
getItemLayout(data, index) {
return { length: styles.listItem.height, offset: styles.listItem.height * index, index };
}
render() {
return (
<View style={styles.container}>
<FlatList
ref={(ref) => this.flatListRef = ref}
data={DATA}
renderItem={this.renderItem.bind(this)}
keyExtractor={(item) => item.id}
getItemLayout={this.getItemLayout.bind(this)} />
<StatusBar style="auto" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
marginTop: Constants.statusBarHeight
},
listItem: {
flex: 1,
height: 40,
backgroundColor: 'yellow',
justifyContent: 'center',
paddingLeft: 10
}
});
const DATA = [...Array(20).keys()].map((key) => ({ id: key.toString(), data: 'Item ' + key.toString()}));
The above shows how to get FlatList’s builtin scrollToIndex
function working by implementing getItemLayout
, which is required to use scrollToIndex
as per the documentation. This is relatively easy in this case because our listItem style specifies a fixed item height. If specifying a fixed height is possible for your use case, this is an easy solution and will lend your FlatList some performance benefit by having getItemLayout
implemented. If it’s not possible to simply give your items a fixed height, move to case 2.
Case 2: scrollToIndex with Dynamic Item Size
import { StatusBar } from 'expo-status-bar';
import Constants from 'expo-constants';
import React from 'react';
import { StyleSheet, Text, View, FlatList } from 'react-native';
export default class App extends React.Component {
constructor(props) {
super(props);
this.flatListRef = null;
this.heights = Array(DATA.length);
}
componentDidMount() {
setTimeout(() => this.flatListRef.scrollToIndex({ index: 127 }), 1000);
}
scrollToIndexFailed(error) {
const offset = error.averageItemLength * error.index;
this.flatListRef.scrollToOffset({offset});
setTimeout(() => this.flatListRef.scrollToIndex({ index: error.index }), 100); // You may choose to skip this line if the above typically works well because your average item height is accurate.
}
renderItem({ item, index }) {
return (
<View style={styles.listItem}>
<Text>{item.data}</Text>
</View>
);
}
render() {
return (
<View style={styles.container}>
<FlatList
ref={(ref) => this.flatListRef = ref}
data={DATA}
renderItem={this.renderItem.bind(this)}
keyExtractor={(item) => item.id}
onScrollToIndexFailed={this.scrollToIndexFailed.bind(this)} />
<StatusBar style="auto" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
marginTop: Constants.statusBarHeight
},
listItem: {
flex: 1,
backgroundColor: 'yellow',
justifyContent: 'center',
}
});
const DATA = [...Array(200).keys()].map((key) => ({ id: key.toString(), data: ('Item ' + key.toString()).repeat(Math.floor(Math.random() * 29)+1)}));
If you have a variable item size, as in the case above, you may not easily be able to implement getItemLayout
meaning that React Native’s FlatList scrollToIndex
will only be able to scroll to items that are close to the currently seen items and scrolling far down the list can cause the method to fail. When this happens, we pass the onScrollToIndexFailed
prop to catch the error and attempt to scroll to the location using the average height. This will trigger a loading of more items, and the app will recursively scroll to the location until it no longer fails.
Conclusion
It is easier to use scrollToIndex
with items of a fixed size, but you are not out of luck if your items are of a dynamic size. It is still possible to create a decent user experience this way, it will just take a bit longer for the scrolling to get worked out!