SiSa
SiSa

Reputation: 2684

Prevent FlatList scrolling when new items are added

I have an inverted vertical FlatList in my chat app, which shows the newest message at the bottom and the oldest message at the top (like all other chat applications)
The problem is when I want to add new messages to the bottom of my list, FlatList automatically jumps to the bottom end of the list!
All I need is to prevent scrolling in this situation

Here is my FlatList:

<FlatList
  inverted
  style={{flex: 1}}
  data={this.state.data}
  keyExtractor={(item, index) => item.id}
  renderItem={this.renderItem}
/>

And here is the code to add the newest messages to the list

const data = [ ...newMessages, ...this.state.data ];
this.setState({ data });

Upvotes: 21

Views: 7149

Answers (5)

fabOnReact
fabOnReact

Reputation: 5942

you can control this in react-native using the prop https://reactnative.dev/docs/next/scrollview#maintainvisiblecontentposition

As you can see from this video in PR #35319, you can modify this behaviour and avoid ScrollView to change position.

You can check their iOS Example and Android examples to better understand the API.

maintainvisiblecontentposition

When set, the scroll view will adjust the scroll position so that the first child that is currently visible and at or beyond minIndexForVisible will not change position. This is useful for lists that are loading content in both directions, e.g. a chat thread, where new messages coming in might otherwise cause the scroll position to jump. A value of 0 is common, but other values such as 1 can be used to skip loading spinners or other content that should not maintain position.

The optional autoscrollToTopThreshold can be used to make the content automatically scroll to the top after making the adjustment if the user was within the threshold of the top before the adjustment was made. This is also useful for chat-like applications where you want to see new messages scroll into place, but not if the user has scrolled up a ways and it would be disruptive to scroll a bunch.

Caveat 1: Reordering elements in the scrollview with this enabled will probably cause jumpiness and jank. It can be fixed, but there are currently no plans to do so. For now, don't re-order the content of any ScrollViews or Lists that use this feature.

Caveat 2: This uses contentOffset and frame.origin in native code to compute visibility. Occlusion, transforms, and other complexity won't be taken into account as to whether content is "visible" or not.

https://github.com/facebook/react-native/pull/34141#issuecomment-1180556939

https://github.com/facebook/react-native/pull/35049

https://github.com/facebook/react-native/pull/35319

Upvotes: 1

Iman Maleki
Iman Maleki

Reputation: 644

You can use onViewableItemsChanged and scrollToIndex together to keep the viewableItems as per previous state.

const flatListRef = React.useRef();
const startIndexRef = React.useRef(0);
  
const handleListChange = () => {
  // ...otherlogic

  if (haseAddedToStart)
    flatListRef.current?.scrollToIndex({
      index: startIndexRef.current + 1,
      animated: false,
    });      
};

const onViewableItemsChanged = React.useCallback(({ viewableItems }) => {
  startIndexRef.current = viewableItems[0].index;
}, []);

Extra props to be added to Flat List are ref and onViewableItemsChanged

 <FlatList              
          ref={flatListRef}
          onViewableItemsChanged={onViewableItemsChanged}
          // ...otherProps
        />

onViewableItemsChanged should come with useCallback, as changes to list would cause error.

It will scroll smaller than item height if the first item is half visible.

A more accurate result should be achievable via onScrollEndDrag and onMomentumScrollEnd but it needs calculating items height.

Upvotes: 0

Hamidreza Alizade
Hamidreza Alizade

Reputation: 1

Put this in the view

<FlatList
  data={this.state.data}
      ref={ref => {
        this.flatListRef = ref;
      }}
      initialScrollIndex={0}
      keyExtractor={item => item.id}
      extraData={this.state.refresh}
      renderItem={({item, index}) => {
        // animation={animation}
        return (
          <ListItem
            inAnimation={inAnimation}
            outAnimation={outAnimation}
            duration={duration}
            easing={easing}
            isDeleted={item._isDeleted}
            id={item.id}
            item={item}
          />
        );
      }}
    `/>`

Run this in a function

this.flatListRef.scrollToIndex({
       animated: true,
       index: nextProps.indexScroll})

  });

Upvotes: 0

Jurrian
Jurrian

Reputation: 849

You should use a workaround to achieve this. If you check the documentation for FlatList (which extends the props of ScrollView), you'll see that all you need to do is set the scrollEnabled prop to false to disable scrolling. How and where you choose to do this will be up to you since you didn't really post a lot of code. A simple way to handle this would be to use state:

<FlatList
  ...
  scrollEnabled={this.state.scrollEnabled}
/>

In your case you could change the state when the new data is being loaded and change it back when it is rendered.

There is an open issue on Github about this case.

Upvotes: -1

Prabhunath Yadav
Prabhunath Yadav

Reputation: 185

Your case look simple but you are adding new message at top then reverse it to bottom last position using inverted flag

Could remove inverted flag and add new item at last simply const data = [...this.state.data, ...newMessages];

<FlatList
  style={{flex: 1}}
  data={this.state.data}
  keyExtractor={(item, index) => item.id}
  renderItem={this.renderItem}
/>

const data = [...this.state.data, ...newMessages];
this.setState({ data });

I hope this will work

Upvotes: 0

Related Questions