React-native – Efficient way to scroll to certain index in FlatList with variable item size

react-native

I'm having a trouble adding scroll/jump to certain index functionality on FlatList in react-native. My FlatList items are vary in size (height) which makes me unable to implement getItemLayout since this requires me to have prior knowledge about the FlatList item size, therefore I cannot use scrollToIndex (which requires getItemLayout to be implemented).

My solution was to get each item's size when rendered by using onLayout and map them with their index. I can then use each item size to get their offset and use scrollToOffset to jump to the given item (by using scrollToItem function in the code below). The issue here is that I am not able to jump to certain item until that item has been rendered.
My temporary solution for that is by tweaking initialNumberToRender close to the number of data and set the windowSize props as high as possible so that all of the items will be rendered (even though the user doesn't scroll down).

getOffsetByIndex = (index) => {
    let offset = 0;
    for (let i = 0; i < index; i++) {
      const elementLayout = this.layoutMap[index];
      if (elementLayout && elementLayout.height) {
        offset += this.layoutMap[i].height;
      }
    }
    return offset;
};

scrollToItem = (index) => {
  const offset = this.getOffsetByIndex(index);
  this.flatListRef.scrollToOffset(offset);
};

addToLayoutMap = (layout, index) => {
  this.layoutMap[index] = layout;
};

render(){
  return(
    <FlatList
      ref={this.flatListRef}
      data={this.state.data}
      renderItem={() => <View onLayout={this.addToLayoutMap}> <SomeComponent/> </View>}
      initialNumToRender={this.state.data.length / 5}
      windowSize={this.state.data.length}
    />
  );
}

This solution works with small number of data, but when the data is large (containing ~300 of rows), it will take long time to be rendered get all the item size, preventing the user to jump to the last item directly for example.

Is there any efficient way to do it?
Also, rendering all the rows is so memory consumptive and negates the benefit of using FlatList.

Best Answer

You can dynamically split your data according to scroll direction. If scroll goes up prepend data to your state and same for opposite direction. Then use onScrollToIndexFailed like this :

<FlatList
  ref={this.flatListRef}
  data={this.state.data}
  renderItem={() => <View> <SomeComponent /> </View>}
  initialNumToRender={this.state.data.length / 5}
  onEndReached={(e) => {
    // Append data
  }}
  onScroll={(e) => {
    if (e.nativeEvent.contentOffset.y == 0) {
      // Prepend data
    }
  }}
  onScrollToIndexFailed={(error) => {
    this.flatListRef.scrollToOffset({ offset: error.averageItemLength * error.index, animated: true });
    setTimeout(() => {
      if (this.state.data.length !== 0 && this.flatListRef !== null) {
        this.flatListRef.scrollToIndex({ index: error.index, animated: true });
      }
    }, 100);
  }}
/>   

You can workaround this issue. This worked for me and took me a lot of time to get that work :))

Related Topic