0
votes

I'm trying to setup a chat in react native and want to check if the bottom of my FlatList is reached, every time I reach it, so I'm able to scroll to the bottom when the keyboard is opening and the user was on the end of the list before (if she wasn't at the end, I don't want to scroll down).

Of course I could call onEndReached, but this fires only once when all messages are loaded.

So I was coding following, but asked myself if there is a better way to achieve this:

My states and my FlatList ref:

  const [messageInputFocus, setmessageInputFocus] = useState(false);
  const [
    messagesListDistanceFromEnd,
    setMessagesListDistanceFromEnd,
  ] = useState(0);
  const [messagesListScrollYPos, setMessagesListScrollYPos] = useState(0);
  const inboxThreadFlatList = useRef<FlatList>(null);

Use useEffect when the input of the message was focued:

  useEffect(() => {
    if (messagesListDistanceFromEnd === messagesListScrollYPos) {
      inboxThreadFlatList.current?.scrollToEnd({ animated: true });
    }
  }, [messageInputFocus]);

  const onScrollMessagesList = (
    event: NativeSyntheticEvent<NativeScrollEvent>
  ) => {
    setMessagesListScrollYPos(event.nativeEvent.contentOffset.y);
  };

My FlatList with onEndReached and onScroll methods at the bottom:

          <FlatList
            data={messages.records}
            renderItem={({ item, index }) => {
              return (
                <View
                  style={
                    index === messages.length - 1 ? { paddingBottom: 68 } : {}
                  }
                >
                  {userMe.id === item.sender ? (
                    <View
                      style={[
                        styles.messageContainer,
                        styles.messageSentContainer,
                      ]}
                    >
                      <View>
                        <Text
                          style={[styles.messageText, styles.messageSentText]}
                        >
                          {item.message}
                        </Text>
                        <View
                          style={[styles.triangle, styles.triangleSent]}
                        ></View>
                      </View>
                    </View>
                  ) : (
                    <View
                      style={[
                        styles.messageContainer,
                        styles.messageReceivedContainer,
                      ]}
                    >
                      <View>
                        <Text
                          style={[
                            styles.messageText,
                            styles.messageReceivedText,
                          ]}
                        >
                          {item.message}
                        </Text>
                        <View
                          style={[styles.triangle, styles.triangleReceived]}
                        ></View>
                      </View>
                    </View>
                  )}
                </View>
              );
            }}
            ref={inboxThreadFlatList}
            keyExtractor={(item, index) => index.toString()}
            initialScrollIndex={messages.length - 1}
            onScrollToIndexFailed={(info) => {
              const wait = new Promise((resolve) => setTimeout(resolve, 500));
              wait.then(() => {
                inboxThreadFlatList.current?.scrollToIndex({
                  index: info.index,
                  animated: true,
                });
              });
            }}
            onEndReached={({ distanceFromEnd }) => {
              setMessagesListDistanceFromEnd(distanceFromEnd);
            }}
            onScroll={onScrollMessagesList}
          />

The code works like that. But again, is there a better way, since I set the my scroll position in a state and ask myself, if that's a performance issue?

1

1 Answers

0
votes

So there's a reason why inverted on FlatList exists :)

If FlatList is inverted I am able to check if event.nativeEvent.contentOffset.y is zero and then set a state once like that:

const onScrollMessagesList = (
    event: NativeSyntheticEvent<NativeScrollEvent>
) => {
    if (event.nativeEvent.contentOffset.y === 0 && !messagesListScrolledToEnd) {
         setMessagesListScrolledToEnd(true);
    } else if (messagesListScrolledToEnd) {
         setMessagesListScrolledToEnd(false);
    }
};

And then check if my TextInput is focused, if so, scroll to index 0:

useEffect(() => {
    if (messagesListScrolledToEnd) {
      scrollToBottom();
    }
}, [messageInputFocus]);

const scrollToBottom = () => {
    setTimeout(() => {
      inboxThreadFlatList.current?.scrollToIndex({
        animated: true,
        index: 0,
      });
    }, 50);
};

I hope, that this helps anybody.