Prevent inverted Flatlist from scrolling to bottom when new items are added

Viewed 5389

I am building a chat app, using an inverted Flatlist. I add new items to the top of the list when onEndReached is called and everything works fine.

The problem is that if add items to the bottom, it instantly scrolls to the bottom of the list. That means that the user has to scroll back up to read the messages that were just added (which is terrible).

I tried to call scrollToOffset in onContentSizeChange, but this has a one-second delay where the scroll jumps back and forth.

How can I have the list behave the same way when I add items to the top AND to the bottom, by keeping the same messages on screen instead of showing the new ones?

4 Answers

here is demo: https://snack.expo.io/@nomi9995/flatlisttest

Solution 1:

use maintainVisibleContentPosition props for preventing auto scroll in IOS but unfortunately, it's not working on android. but here is PR for android Pull Request. before merge this PR you can patch by own from this PR

<FlatList
  ref={(ref) => { this.chatFlatList = ref; }}
  style={styles.flatList}
  data={this.state.items}
  renderItem={this._renderItem}
  maintainVisibleContentPosition={{
     minIndexForVisible: 0,
  }}
/>

Solution 2:

I found another workaround by keep latest y offset with onScroll and also save content height before and after adding new items with onContentSizeChange and calculate the difference of content height, and set new y offset to the latest y offset + content height difference!

Here I am adding a new item on top and bottom in an inverted Flatlist.

enter image description here

I hope you can compare your requirements with the provided sample code :)

Full Code:


import React, {Component} from 'react';
import {
  SafeAreaView,
  View,
  FlatList,
  StyleSheet,
  Text,
  Button,
  Platform,
  UIManager,
  LayoutAnimation,
} from 'react-native';

if (Platform.OS === 'android') {
  if (UIManager.setLayoutAnimationEnabledExperimental) {
    UIManager.setLayoutAnimationEnabledExperimental(true);
  }
}

const getRandomColor = () => {
  var letters = '0123456789ABCDEF';
  var color = '#';
  for (var i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
};

const DATA = [
  getRandomColor(),
  getRandomColor(),
  getRandomColor(),
  getRandomColor(),
  getRandomColor(),
];

export default class App extends Component {
  scrollValue = 0;
  append = true;

  state = {
    data: DATA,
  };

  addItem = (top) => {
    const {data} = this.state;
    let newData;
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
    if (top) {
      newData = [...data, getRandomColor()];
      this.setState({data: newData});
    } else {
      newData = [getRandomColor(), ...data];
      this.setState({data: newData});
    }
  };

  shouldComponentUpdate() {
    return this.scrollValue === 0 || this.append;
  }

  onScrollBeginDrag = () => {
    this.append = true;
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
    this.setState({});
  };

  render() {
    const {data} = this.state;
    return (
      <SafeAreaView style={styles.container}>
        <Button title="ADD ON TOP" onPress={() => this.addItem(true)} />
        <FlatList
          data={data}
          onScrollBeginDrag={this.onScrollBeginDrag}
          renderItem={({item}) => <Item item={item} />}
          keyExtractor={(item) => item}
          inverted
          onScroll={(e) => {
            this.append = false;
            this.scrollValue = e.nativeEvent.contentOffset.y;
          }}
        />
        <Button title="ADD ON BOTTOM" onPress={() => this.addItem(false)} />
      </SafeAreaView>
    );
  }
}

function Item({item}) {
  return (
    <View style={[styles.item, {backgroundColor: item}]}>
      <Text style={styles.title}>{item}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  item: {
    backgroundColor: '#f9c2ff',
    padding: 20,
    height: 100,
  },
  title: {
    fontSize: 32,
  },
});

This is one year late, but this works fine:

        <FlatList
          inverted
          initialScrollIndex={1}
          {...}
        />

Since inverted renders flatlist but with inverted: 1, thus you need to pass 1 to initialScrollIndex so that it scrolls to bottom in normal list and to top in the inverted one

Related