React Native FlatList scrollToIndex Function to Scroll to Item with Index with Fixed or Variable Unknown Row Size

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

Screenshot of react native flatlist scrollToIndex being used with fixed row 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

Screenshot of react native flatlist scrollToIndex being used with variable row 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!

Leave a Reply

Your email address will not be published. Required fields are marked *