7
votes

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

4 Answers

3
votes

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!

0
votes

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,
  },
});

0
votes

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

-1
votes

Have you tried using keyExtractor? It may help react avoid re-render, so try use unique keys for each item. you can read more about it here: https://reactnative.dev/docs/flatlist#keyextractor