39

I am trying to create a chat in React native using a <Flatlist />

Like WhatsApp and other chat apps, the messages start at the bottom.

After fetching the messages from my API, I call

this.myFlatList.scrollToEnd({animated: false});

But it scrolls somewhere in the middle and sometimes with fewer items to the bottom and sometimes it does nothing.

How can I scroll initially to the bottom?

My chat messages have different heights, so I can't calculate the height.

Ray
  • 2,713
  • 3
  • 29
  • 61
yooouuri
  • 2,578
  • 10
  • 32
  • 54

15 Answers15

46

I had similar issue. If you want to have you chat messages start at the bottom, you could set "inverted" to true and display your messages and time tag in an opposite direction.

Check here for "inverted" property for FlatList. https://facebook.github.io/react-native/docs/flatlist#inverted

If you want to have you chat messages start at the top, which is what I am trying to achieve. I could not find a solution in FlatList, because as you said, the heights are different, I could not use getItemLayout which make "scrollToEnd" behave in a strange way.

I follow the approach that @My Mai mentioned, using ScrollView instead and do scrollToEnd({animated: false}) in a setTimeout function. Besides, I added a state to hide the content until scrollToEnd is done, so user would not be seeing any scrolling.

CAIsoul
  • 485
  • 5
  • 6
  • 29
    I used your inverted solution and changed the flex-direction to **column-reverse**. Now it works as expected: `` – Johnny Fekete Jan 21 '21 at 22:28
  • @Johnny Fekete This actually renders the list in the opposite order. Did you manage to reverse the list? Or did you found a better way? – Josh Liu Aug 18 '21 at 07:10
  • 1
    thank you very much Mr. @JohnnyFekete – Rafiq Sep 25 '21 at 23:33
  • 1
    This worked for me at first, then I tried rendering many chat messages and it makes a weird scroll effect when mounting, it was caused by ```flexDirection: 'column-reverse'.``` Instead calling ```reverse()``` in the data array is not the best for performance but it seems to be the solution that is glitch free – fermmm Nov 28 '21 at 01:20
  • What can I do if I have a very large list? It is very slow and visibly loading when I use column-reverse, which looks terrible. – Arcanus Mar 12 '22 at 23:25
19

I solved this issue with inverted property and reverse function

https://facebook.github.io/react-native/docs/flatlist#inverted

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse

<FlatList
   inverted
   data={[...data].reverse()}
   renderItem={renderItem}
   keyExtractor={(item) => item.id}
/>

You can use this solution in chat component.

Andrei
  • 279
  • 2
  • 6
12

Set initialScrollIndex to your data set's length - 1.

I.e.

<Flatlist
data={dataSet}
initialScrollIndex={dataSet.length - 1}
/>
peralmq
  • 2,160
  • 1
  • 22
  • 20
  • 18
    Getting Exception error on this: `scrollToIndex should be used in conjunction with getItemLayout or onScrollToIndexFailed` – douglasrcjames Oct 01 '20 at 22:29
8

I faced the same issue with you and then I moved to use ScrollView. It is fixed:

componentDidMount() {
  setTimeout(() => {
    this.scrollView.scrollToEnd();
  });
}

<ScrollView ref={(ref) => { this.scrollView = ref; }} style={styles.messages}>
      {
        messages.map((item, i) => (
          <Message 
            key={i} 
            direction={item.userType === 'banker' ? 'right' : 'left'} 
            text={item.message} 
            name={item.name}
            time={item.createdAt}
          />
        ))
      }
    </ScrollView>`
My Mai
  • 1,073
  • 9
  • 10
6

There are two types of 'good' solutions as of 2021. First one is with timeout, references and useEffect. Here's the full example using Functional Components and Typescript:

   // Set the height of every item of the list, to improve perfomance and later use in the getItemLayout
   const ITEM_HEIGHT = 100;

   // Data that will be displayed in the FlatList
   const [data, setData] = React.useState<DataType>();

   // The variable that will hold the reference of the FlatList
   const flatListRef = React.useRef<FlatList>(null);

   // The effect that will always run whenever there's a change to the data
   React.useLayoutEffect(() => {
    const timeout = setTimeout(() => {
      if (flatListRef.current && data && data.length > 0) {
        flatListRef.current.scrollToEnd({ animated: true });
      }
    }, 1000);

    return () => {
      clearTimeout(timeout);
    };

   }, [data]);

   // Your FlatList component that will receive ref, data and other properties as needed, you also have to use getItemLayout
   <FlatList
     data={data}
     ref={flatListRef}
     getItemLayout={(data, index) => {
              return { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index };
     }}
     { ...otherProperties } 
   />

With the example above you can have a fluid and animated scroll to bottom. Recommended for when you receive a new message and has to scroll to the bottom, for example.

Apart from this, the second and easier way is by implementing the initialScrollIndex property that will instantly loads the list at the bottom, like that chat apps you mentioned. It will work fine when opening the chat screen for the first time.

Like this:

   // No need to use useEffect, timeout and references... 
   // Just use getItemLayout and initialScrollIndex.

   // Set the height of every item of the list, to improve perfomance and later use in the getItemLayout
   const ITEM_HEIGHT = 100;

   <FlatList
     data={data}
     getItemLayout={(data, index) => {
       return { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }; 
     }}
     { ...otherProperties } 
   />

Frederiko Ribeiro
  • 1,844
  • 1
  • 18
  • 30
2

I found a solution that worked for me 100%

Added the ref flatListRef to my flatlist:

<Flatlist
     reference={(ref) => this.flatListRef = ref}
     data={data}
     keyExtractor={keyExtractor}
     renderItem={renderItem}
/>

Then whenever you want to automatically scroll to bottom of the list use:

this.flatListRef._listRef._scrollRef.scrollToEnd({ animating: true });

yes you should access the element _listRef then _scrollRef then call the scrollToEnd


  • react-native 0.64.1
  • react 17.0.2

Mostav
  • 2,203
  • 15
  • 27
1

<FlatList contentContainerStyle={{ flex: 1, justifyContent: 'flex-end' }} />

Ray
  • 2,713
  • 3
  • 29
  • 61
Martin Lockett
  • 2,275
  • 27
  • 31
1

I've struggled on this as well and found the best possible solution for me that renders without a glitch is:

  1. Use inverted={-1} props
  2. Reverse the order of messages objects inside my array with data={MyArrayofMessages.reverse()} in my case data={this.state.messages.reverse()} using reverse() javascript function.

Stupidly easy and renders instantaneously !

Dharman
  • 30,962
  • 25
  • 85
  • 135
  • if he want to use Array.reverse(); he don't want to use inverted flatlist. because Array.reverse() take time to process – ucup Jul 25 '21 at 01:48
1

Use inverted={1} and reverse your data by using the JS reverse function. It worked for me

Abdullah
  • 101
  • 1
  • 5
1

I found a solution that worked for me 100%

  let scrollRef = React.useRef(null)

and

<FlatList
   ref={(it) => (scrollRef.current = it)}
   onContentSizeChange={() =>
        scrollRef.current?.scrollToEnd({animated: false})
    }
    data={data}/>
0

I am guessing that RN cannot guess your layout so it cannot know how much it needs to "move". According to the scroll methods in the docs you might need to implement a getItemLayout function, so RN can tell how much it needs to scroll.

https://facebook.github.io/react-native/docs/flatlist.html#scrolltoend

sebastianf182
  • 9,844
  • 3
  • 34
  • 66
0

Guys if you want FlatList scroll to bottom at initial render. Just added inverted={-1} to your FlatList. I have struggle with scroll to bottom for couple of hours but it ends up with inverted={-1}. Don't need to think to much about measure the height of FlatList items dynamically using getItemLayout and initialScrollIndex or whats so ever.

Bruce
  • 457
  • 1
  • 5
  • 18
0

  1. List item


Heading


/>

This method worked for me and it is currently working. Thanks @arjantin.## Heading ##

omar
  • 11
  • 2
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 23 '23 at 14:17
-1

If you want to display the message inverted, set "inverted" to true in the flat list.

<Flatlist
data={messageData}
inverted={true}
horizontal={false}
/>

If you just want to scroll to the last message, you can use initialScrollIndex

<Flatlist
data={messageData}
initialScrollIndex={messageArray.length - 1}
horizontal={false}
/>
GhostMode
  • 37
  • 2
  • This solution alway throws: Invariant Violation: scrollToIndex should be used in conjunction with getItemLayout or onScrollToIndexFailed, otherwise there is no way to know the location of offscreen indices or handle failures. – ppulwey Nov 02 '21 at 08:01
  • This works for me. I don't know why someone decrease point. `inverted` feature is not usable for chat messages. – Baris Senyerli Apr 19 '22 at 11:37
  • the question is was for how to navigate to last item, it may be images list, products list or some thing else. so inverted willl only reverse your array and show the last item on the top. we need to scrolltoEnd functionality. thanks – Engr.Aftab Ufaq Oct 05 '22 at 13:49
-1

I spent couple of hours struggling with showing the first message on top without being able to calculate the item's height as it contains links and messages. But finally i've been able to...

What i've done is that i wrapped the FlatList in a View, set FlatList as inverted, made it to take all available space and then justified content. So now, conversations with few messages starts at top but when there are multiple messages, they will end on bottom. Something like this:

<View style={ConversationStyle.container}>
    <FlatList
        data={conversations}
        initialNumToRender={10}
        renderItem={({ item }) => (
            <SmsConversationItem
                item={item}
                onDelete={onDelete}
            />
        )}
        keyExtractor={(item) => item.id}
        getItemCount={getItemCount}
        getItem={getItem}
        contentContainerStyle={ConversationStyle.virtualizedListContainer}
        inverted // This will make items in reversed order but will make all of them start from bottom
    />
</View>

And my style looks like this:

const ConversationStyle = StyleSheet.create({
    container: {
        flex: 1
    },

    virtualizedListContainer: {
        flexGrow: 1,
        justifyContent: 'flex-end'
    }
};

Dharman
  • 30,962
  • 25
  • 85
  • 135