Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flatlist onEndReached endless loop

I am using state to store the following data.

state = {
    refresh: true,
    isFetching: true,
    showLoadingBottom: false,
    data: []
};

on componentDidMount I manually call a function _fetchData which loads data into this.state.data.

When the flatlist is scrolled to the end it fires _fetchData twice which ends up returning the same data twice ( which is another problem, why does it fire twice? ).

Once the flatlist reaches the end ie, no more data is returned from the server, it goes into an endless loop since the onEndReached continuously fires over and over again even though no new data is returned from the server and this.state.data remains the same.

This is my render code

render() {

    return (

        <View
            style={{
                flex: 1
            }}>

            <FlatList

                refreshControl={
                    <RefreshControl
                        refreshing={this.state.refresh}
                        onRefresh={() => {
                            this.setState({
                                refresh: true
                            }, this._fetchData);
                        }}
                        title={"Pull To Refresh"}
                        tintColor={darkGrey}
                        titleColor={darkGrey}/>
                }

                onEndReachedThreshold={0.5}
                onEndReached={() => {
                    this.setState({
                        showLoadingBottom: true
                    }, () => {
                        this._fetchData();
                    });

                }}

                showsVerticalScrollIndicator={false}

                data={this.state.data}

                ListFooterComponent={() => {
                    return (
                        this.state.showLoadingBottom &&
                        <View style={{padding: 10}}>
                            <ActivityIndicator size="small" color={colorAccent}/>
                        </View>
                    );
                }}

                renderItem={this._renderItem}

                keyExtractor={(item) => item.id.toString()}

                removeClippedSubviews={true}

            />

        </View>

    );
}
like image 964
Jude Fernandes Avatar asked May 16 '18 19:05

Jude Fernandes


2 Answers

Here is my solution that can maybe be changed to suit other peoples needs: Basically the important parts are onEndReached={this.state.moreData && this.retrieveMore}. So you can test inside your onEndReached function weather there is anymore data (In my case if we only return 1 object i know it's finished) then set state this.state.moreData to false.

<SafeAreaView style={styles.container}>
    <FlatList
        data={Object.values(this.state.documentData)}
        // Render Items
        renderItem={({ item }) => (
            <ItemSelector
                item={item}
                onPress={() => {this.selectItem(item)}}
            />
        )}
        // On End Reached (Takes in a function)
        onEndReached={this.state.moreData && this.retrieveMore}
        // How Close To The End Of List Until Next Data Request Is Made
        onEndReachedThreshold={1}
        ListEmptyComponent={
            <Text>No jobs to show</Text>
        }
    />
</SafeAreaView>

retrieveMore = async () => {
    try {

        // Set State: Refreshing
        this._isMounted && this.setState({ refreshing: true });

        fbDb.ref('job')
            .orderByKey()
            .startAt(this.state.lastVisible) //Start at the last item we found
            .limitToFirst(this.state.limit) //Limit queries returned per page
            .once('value', snapshot => {

                //check if we got a result
                if(snapshot.numChildren() > 1){

                    .....
                    this._isMounted && this.setState({
                        documentData: newstate,
                        lastVisible: lastVisible,
                        refreshing: false, //Hide loading icon
                    });
                } else {
                    this._isMounted && this.setState({
                        refreshing: false, //Hide loading icon
                        moreData: false
                    });
                }
            });
    }
    catch (error) {
        console.log(error);
    }
};
like image 195
MomasVII Avatar answered Nov 04 '22 00:11

MomasVII


I have a similar problem. In my case it is because the ListFooterComponent.

If you render the ListFooterComponent with this pattern or equivalent

onEndReachedThreshold={x} // for any x >= 0
ListFooterComponent={() => {
  if (isDataFetching) {
    return <SomeComponentWithSomeHeight />
  } else {
    return undefined;
  }
}}

It will trigger onEndReached infinitely when the user scrolls down the end of the list (or if your content is not longer than the list's visibility area).

And it is because the presence and absence of the <SomeComponentWithSomeHeight /> affects the height of the content and thus triggers the endReached re-calculation.

FlatList onEndReached infinite loop

And following are the possible solution I can think of.

  1. Use negative onEndReachedThreshold that is always "higher" than the height of the ListFooterComponent. But I don't like this solution because it is difficult to know the "higher" (it is relative to the FlatList's visibility area). And the negative onEndReachedThreshold may cause some issue on Android.

  2. Implement your own loading overlay outside of the FlatList so that the loading component does not affect the content height.

  3. Set opacity = 0 to hide the ListFooterComponent instead of returning undefined, so that it is always there and the content height does not change when it becomes visible.

ListFooterComponent={() => {
  return (
    <View style={{ opacity: isDataFetching ? 1 : 0 }}>
      <SomeComponentWithSomeHeight />
    </View>
  );
}}
like image 1
asinkxcoswt Avatar answered Nov 04 '22 00:11

asinkxcoswt