Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FlatList] onEndReached triggered 2 times #14015

Closed
fengshanjian opened this issue May 17, 2017 · 29 comments
Closed

[FlatList] onEndReached triggered 2 times #14015

fengshanjian opened this issue May 17, 2017 · 29 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@fengshanjian
Copy link

fengshanjian commented May 17, 2017

<FlatList
ref={(flatList)=>this._flatList = flatList}
ListHeaderComponent={this._header}
ListFooterComponent={this._footer}
ItemSeparatorComponent={this._separator}
renderItem={this._renderItem}
getItemLayout={(data,index)=>({length: ITEM_HEIGHT, offset: (ITEM_HEIGHT+2) * index, index})}
onEndReachedThreshold={0.5}
onEndReached={(info)=>{
console.log(JSON.stringify(info));
}}
data={data}
/>

When the scroll speed is fast enough,onEndReached triggered 2 times or more times

Is there any solution?

@parkerproject
Copy link

mine doesn't even trigger.

@fengshanjian
Copy link
Author

@parkerproject
My paltform is ios 10.3,mac osx 10.12.5

@benderlidze
Copy link

benderlidze commented May 18, 2017

@fengshanjian
Yep, got the same - fires 2 times. But if you are using redux or even state - you can use something like this

onEndReached={(info)=>{
if (this.state.loading===false){
console.log(JSON.stringify(info));
}
}}

@JonoH
Copy link

JonoH commented May 31, 2017

Same issue my side. Seems to be a bug in the ScrollView bouncing feature on iOS (when you over scroll on iOS the ScrollView scrolls past the content and bounces back to the end of the content by default).

Setting bounces={false} solved onEndReached being called twice for me. Still not an ideal solution though as you lose that nice smooth/fluid feel in the UX.

@hramos hramos changed the title FlatList onEndReached triggered 2 times [FlatList] onEndReached triggered 2 times Jun 2, 2017
@hramos
Copy link
Contributor

hramos commented Jun 2, 2017

Hey, thanks for reporting this issue!

It looks like your description is missing some necessary information. Can you please add all the details specified in the template? This is necessary for people to be able to understand and reproduce the issue being reported.

@grin
Copy link

grin commented Jun 23, 2017

I'm definitely seeing the second onEndReached call triggered by the bouncing effect on iOS. Adding bounces={false} to the FlatList fixed it for me. I think ideally, RN should not call onEndReached while the list is in the "bouncing" state. Not sure how doable is this though.

@grin
Copy link

grin commented Jun 23, 2017

OK, here is my solution for how to avoid the second onEndReached call with the bouncing effect enabled on iOS:

  1. Add onMomentumScrollBegin prop to your FlatList declaration.
        <FlatList
          data={this.props.data}
          onEndReached={...}
          onEndReachedThreshold={0.5}
          ...
          onMomentumScrollBegin={() => { this.onEndReachedCalledDuringMomentum = false; }}
        />
  1. Modify your onEndReached callback to trigger data fetching only once per momentum.
  onEndReached = () => {
    if (!this.onEndReachedCalledDuringMomentum) {
      this.props.fetchData();
      this.onEndReachedCalledDuringMomentum = true;
    }
  };

Tested with RN 0.44.0.

@gonglong
Copy link

hi @grin can you pls refer me some document about the function onMomentumScrollBegin? i cannot find any on http://facebook.github.io/react-native/releases/0.46/docs/flatlist.html

@vomchik
Copy link

vomchik commented Jul 31, 2017

@vomchik
Copy link

vomchik commented Jul 31, 2017

FlatList is just wrapper of the ScrollView

@hramos
Copy link
Contributor

hramos commented Jul 31, 2017

Hey, thanks for reporting this issue!

It looks like your description is missing some necessary information, or the list of reproduction steps is not complete. Can you please add all the details specified in the template? This is necessary for people to be able to understand and reproduce the issue being reported.

I am going to close this, but feel free to open a new issue with the additional information provided. Thanks!

@hramos hramos closed this as completed Jul 31, 2017
@ezha86
Copy link

ezha86 commented Aug 5, 2017

many thanks @grin. i faced with multiple call onEndReached and your solution decrease my api call by 2. I fixed it by adding load state validation:
if (!this.onEndReachedCalledDuringMomentum && !this.state.loading)
works for me

@CodeRabbitYu
Copy link

@ezha86 Can you look at the code?

@zachrnolan
Copy link

I've been having a couple different issues with onEndReached. I recently created an issue with a very detailed breakdown of everything here: #16067.

Hope this is helpful to someone.

@kkkelicheng
Copy link

kkkelicheng commented Sep 26, 2017

My react-native version is 0.44


I have in trouble with onEndReached called much times.
and thanks for friends answers,I get idea from onMomentumScrollBegin and onMomentumScrollEnd.
Here is my solution for share

                    onEndReachedThreshold={0}
                    onEndReached={(info)=>{
                        console.log('onEndReached',info);
                       //need setState ,  otherwise this callback called only once
                        this.setState({
                            // empty this content is also work
                        })
                    }}

                    onMomentumScrollBegin={()=>{
                        console.log('onMomentumScrollStart...')
                    }}
                    onMomentumScrollEnd={()=>{
                        console.log('onMomentumScrollEnd!!!')
                    }}


when you drag list to bottom , you will see log below.
onMomentumScrollStart...
'onEndReached', { distanceFromEnd: -52 }
'onEndReached', { distanceFromEnd: -23 }
'onEndReached', { distanceFromEnd: -8.5 }
.
.
.
onMomentumScrollEnd!!!

so , my solution is do something in onMomentumScrollEnd .
when trigger onEndReached, you set this.shouldLoadMore = true,
when your finger leave list , onMomentumScrollEnd will be called.

 onMomentumScrollEnd={()=>{
         console.log('onMomentumScrollEnd!!!')
         if(this.shouldLoadMore = true){
            //load datas
           this.shouldLoadMore = false
         }
}}




BTW but I still have a question
if not set state or render again , onEndReached called only once.like this:

onEndReached={(info)=>{
                        console.log('onEndReached',info);
                    }}

dose someone kowns the reason ?

@bluhar
Copy link

bluhar commented Nov 20, 2017

@grin solution is good, but when the data is small, there is no scroll bar, it still triggered my onLoadMore() method.

My solution:

class ListContainer extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      selected: (new Map()),
    };
  }

  _keyExtractor = (item, index) => item.ID || item.id;

  _onPressItem = (id, code) => {
    // updater functions are preferred for transactional updates
    this.setState((state) => {
      // copy the map rather than modifying state.
      const selected = new Map(state.selected);
      selected.set(id, !selected.get(id)); // toggle
      return {selected};
    });

    let {onPress} = this.props;
    if (onPress) onPress(id, code);
  };

  _renderItem = ({item}) => {
    if (!!item['loading']) {
      return (
        <ActivityIndicator style={[styles.indicator]}/>
      )
    } else if (!!item['noMoreData']){
      return (
        <Text style={{textAlign:'center', color:Colors.DeepGray, fontSize:16, paddingVertical:5,}}>没有更多数据...</Text>
      )
    } else if (item['empty']){
      return (
        <Text style={{textAlign:'center', color:Colors.DeepGray, fontSize:16, paddingVertical:5,}}>没有数据...</Text>
      )
    }
    else {
      return (
        <ListItem
          id={item.ID || item.id}
          item={item}
          onPressItem={this._onPressItem}
          type={this.props.itemType}
          selected={!!this.state.selected.get(item.id)}
          title={item.title}
        />
      )
    }
  };

  _separator = () => {
    return <View style={{height: 1 / PixelRatio.get(), backgroundColor: Colors.DeepGray, marginHorizontal: 10}}/>;
  };

  _onRefresh = () => {
    let {onRefresh} = this.props;
    onRefresh && onRefresh();
  };

  _onEndReached = (info)=>{
    console.log('1-trigger list onEndReached !', info);
    if (info.distanceFromEnd >= -10) {
      this.props.onLoadMore && this.props.onLoadMore();
    }
  };

  render() {
    return (
      <FlatList
        style={{flex: 1}}
        onEndReachedThreshold={0.05}
        onEndReached={this._onEndReached}
        data={this.props.data}
        extraData={this.state}
        keyExtractor={this._keyExtractor}
        renderItem={this._renderItem}
        ItemSeparatorComponent={this._separator}
        refreshing={this.props.refreshing}
        onRefresh={this.props.onRefresh ? this._onRefresh : null}
      />
    );
  }
}
<ListContainer data={orders} refreshing={isFetching} onRefresh={this._refreshList}
			   onLoadMore={() => {
				 if (!isFetching && !isPaging && hasMoreData) {
				   dispatch(fetchOrdersPage())
				 }
			   }}
			   onPress={(id, orderNo) => {
				 this._openDialog();
				 this.activeOrderNo = orderNo;
			   }}/>

May be it is not a good solution, but works fine in my scenario. For reference only

@jing-wu
Copy link

jing-wu commented Nov 23, 2017

Try this
onEndReachedThreshold={0.01}

@tasneembohra
Copy link

tasneembohra commented Dec 4, 2017

Tried all the above mentioned solution, nothing helped. Still facing this multiple call issue in IOS & Android.

@harish-aka-shivi
Copy link

Was also happening with me, try keeping a flag to stop making request.

if (!this.state.loading) {
    this.setState({loading:true});
    this.setState(state => ({ page: state.page+1}), this.makeNetworkRequest());
}

Then I set the state to loading false after completing of request

@AyubaMakoa
Copy link

`constructor(props){
super(props);
this.state = {
flatListReady:false
}
}

_scrolled(){
this.setState({flatListReady:true})
}

loadMore = () => {
if(!this.state.flatListReady){ return null}

//you can now load more data here from your backend

}

<FlatList
onScroll={this._scrolled.bind(this)}
style={{width:'100%',flexGrow:1}}
ListHeaderComponent={this.headerComponent}
data={this.props.data}
renderItem={this.renderItem}
keyExtractor={(item,index) => index }
onEndReached={(x) => {this.loadMore()}}
onEndReachedThreshold={0.5}

           />

`

@anil1712
Copy link

anil1712 commented Feb 2, 2018

onEndReached is not working as expected. It's not triggering continuously when we reached the end.
FYI: I am using the native-base components.

@gougoushan
Copy link

using setTimeout for setState, eg.
setTimeout(() => this.setState({ arr: [ ...newArr ] }), 3000);

@anil1712
Copy link

@gougoushan Not working for me

@nsyujian
Copy link

don't use onEndReached
react-native-refresh-list-view

  onScroll = ({nativeEvent}) => {
        let previousOffsetY = 0;
        if (this.nativeEvent) {
            previousOffsetY = this.nativeEvent.contentOffset.y;
        }
        const offsetY = nativeEvent.contentOffset.y;

        /**
         * 判断是否为用户拖拽
         */
        if (this.isResponder) {
            /**
             * 判断为上拉并且滚动到底部
             */
            if ((offsetY - previousOffsetY) > 0 && offsetY >= (nativeEvent.contentSize.height + nativeEvent.contentInset.bottom - nativeEvent.layoutMeasurement
                    .height)) {
                if (this.shouldStartFooterRefreshing()) {
                    this.props.onFooterRefresh && this.props.onFooterRefresh(RefreshState.FooterRefreshing);
                }
            }
        }

        this.nativeEvent = nativeEvent;
    }

@mtx62
Copy link

mtx62 commented Apr 9, 2018

@GaoYuJian You are a hero!!

@chunghalu
Copy link

This is my solution:

<FlatList
  ...
  refreshing={this.state.refreshing}
  onRefresh={this._onRefresh}
  onEndReached={this._onEndReached}
  onEndReachedThreshold={0.2}
  onScrollBeginDrag={() => {
    console.log('onScrollBeginDrag');
    this.canAction = true;
  }}
  onScrollEndDrag={() => {
    console.log('onScrollEndDrag');
    this.canAction = false;
  }}
  onMomentumScrollBegin={() => {
    console.log('onMomentumScrollBegin');
    this.canAction = true;
  }}
  onMomentumScrollEnd={() => {
    console.log('onMomentumScrollEnd');
    this.canAction = false;
  }}
/>
_onRefresh = () => {
  console.log('_onRefresh');
  if(!this.canAction) return;
  ...
  };

_onEndReached = () => {
  console.log('_onEndReached');
  if(!this.canAction) return;
  ...
};

@rizwanahmed19
Copy link

For some reasons my onMomentumScrollBegin is not being fired. Is this happening to anybody else?

@pisacode
Copy link

pisacode commented Jul 5, 2018

I have solved it with using debounce from lodash

first import debounce from 'lodash.debounce'

and then just add this to your constructor

this._onEndReached = debounce(this._onEndReached, 500);

@bnbon
Copy link

bnbon commented Jul 23, 2018

Is this going to be fixed in the next release or is the hack necessary?

@facebook facebook locked as resolved and limited conversation to collaborators Jul 31, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests