'React Native FlatList gets infinity loop onRefresh

I'm trying to fetch data from redux to a FlatList and onRefresh={} gives me an infinity loop. When I don't use redux it works but when I moved my fetching into a redux action I'm getting confused.

Can someone describe to me what I am doing wrong?

Thanks in advance.

use my snack: https://snack.expo.io/@thesvarta/redux-with-flatlist

Heres my component

    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import {
      View,
      Text,
      FlatList,
      StatusBar,
      StyleSheet,
      ActivityIndicator,
      TouchableOpacity,
      TouchableHighlight,
      Image,
      Button,
      SearchBar,
      TextInput,
    } from 'react-native';
    import TextDetails from '../TextDetails/TextDetails';
    import {
      fetchDataOptions,
      resetDataOptions,
      selectOptions,
    } from '../store/actions/index';
    class Search extends Component {
      constructor(props) {
        super(props);
        this.state = {
          page: 1,
          loading: false,
          refreshing: false,
        };
      }
      componentDidMount() {
        this.props.onResetDataOptions();
        this.props.onFetchDataOptions(this.state.page, this.props.GroupBy);
        console.log('component_data', this.props.fetch_data_options);
      }
      handleRefresh = () => {
        this.props.onResetDataOptions(); // Resets fetch_data_options
        this.setState(
          {
            page: 1,
            refreshing: true,
          },
          () => {
            this.props.onFetchDataOptions(this.state.page, this.props.GroupBy);
          }
        );
      };

      handleLoadMore = () => {
        if (!this.props.fetch_data_error) {
          this.setState(
            {
              page: this.state.page + 1,
            },
            () => {
              this.props.onFetchDataOptions(this.state.page, this.props.GroupBy);
              console.log('load_more', this.state.page);
            }
          );
        } else {
          this.setState(
            {
              page: 1,
            },
            () => {
              this.props.onFetchDataOptions(this.state.page, this.props.GroupBy);
              console.log('load_more_error', this.state.page);
            }
          );
        }
      };

      renderSeparator = () => {
        return (
          <View
            style={{
              height: 1,
              width: '100%',
              backgroundColor: '#CED0CE',
            }}
          />
        );
      };
      renderFooter = () => {
        if (!this.state.loading) return null;
        return (
          <View
            style={{
              paddingVertical: 20,
              borderTopWidth: 1,
              borderColor: '#CED0CE',
            }}>
            <ActivityIndicator animating size="large" />
          </View>
        );
      };

      renderRow = ({ item, index }) => {
        return (
          <View style={styles.ListContainer}>
            <View style={styles.Text}>
              <TextDetails Size={18}>{item.name}</TextDetails>
            </View>
          </View>
        );
      };
      render() {
        return (
          <View style={styles.SearchContatiner}>
            <View style={styles.Search}>
              <View style={styles.ImageIcon}>
                <Image
                  style={styles.Image}
                  resizeMode="contain"
                  source={require('../images/sokbla.png')}
                />
              </View>
              <View style={styles.InputBox}>
                <TextInput
                  style={styles.InputText}
                  onChangeText={text => this.setState({ query: text })}
                  placeholder={'Search for ' + this.props.Title}
                  value={this.state.query}
                />
              </View>
              <TouchableOpacity
                onPress={() => alert("remove query")}
                style={styles.KryssIcon}>
                <Image
                  style={styles.ImageKryss}
                  resizeMode="contain"
                  source={require('../images/kryssbla.png')}
                />
              </TouchableOpacity>
            </View>
            <View style={styles.Lista}>
              <FlatList
                style={styles.Flatlist}
                data={this.props.fetch_data_options}
                renderItem={this.renderRow}
                keyExtractor={item => item.name}
                ItemSeparatorComponent={this.renderSeparator}
                ListFooterComponent={this.renderFooter}
                onRefresh={this.handleRefresh}
                refreshing={this.state.refreshing}
                onEndReached={this.handleLoadMore}
                onEndReachedThreshold={0}
              />
            </View>
          </View>
        );
      }
    }
    const styles = StyleSheet.create({
      Lista: {
        marginTop: 20,
      },
      Flatlist: {
        width: '100%',
        height: 300,
      },
      ListContainer: {
        flexDirection: 'row',
        width: '100%',
        height: 40,
      },
      Text: {
        marginLeft: '10%',
        width: '70%',
        justifyContent: 'center',
      },
      SearchContatiner: {},
      Search: {
        width: '100%',
        height: 60,
        backgroundColor: 'rgb(240,240,240)',
        flexDirection: 'row',
      },
      Image: {
        width: 23,
        height: 33,
      },
      ImageIcon: {
        justifyContent: 'center',
        width: '15%',
        alignItems: 'center',
      },
      InputBox: {
        width: '70%',
        justifyContent: 'center',
      },
      InputText: {
        paddingLeft: 20,
        width: '100%',
        height: 50,
        fontSize: 20,
      },
      KryssIcon: {
        justifyContent: 'center',
        width: '15%',
        alignItems: 'center',
      },
      ImageKryss: {
        width: 18,
        height: 28,
      },
    });
    const mapStateToProps = state => {
      return {
        fetch_data_options: state.filter.fetch_data_options,
        fetch_data_error: state.filter.error,
        status: state.filter.status,
        
      };
    };
    const mapDispatchToProps = dispatch => {
      return {
        onFetchDataOptions: (page, group_by) =>
          dispatch(fetchDataOptions(page, group_by)),
        onResetDataOptions: () => dispatch(resetDataOptions()),
       
      };
    };
    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(Search);

Here is My Action

    import {
      FETCHING_DATA_OPTIONS,
      FETCH_DATA_OPTIONS_SUCCESS,
      FETCH_DATA_OPTIONS_FAILURE,
      FETCH_DATA_OPTIONS_RESET,
    } from './actionTypes';
    
    export const fetchDataOptions = (page, group_by) => {
      return dispatch => {
        dispatch(getDataOptions());
        return fetch(
          'https://api/getOptions.php?page=' +
            page +
            '&row_per_page=5&group_by=' +
            group_by
        )
          .then(res => res.json())
          .then(json => {
            //$json[0] containts .DATA, .STATUS, .MESSAGE
            //.STATUS can be 200 or 404, if its 404 it means that .DATA is empty              
            if (json[0].STATUS == 200) {
              return dispatch(
                getDataOptionsSuccess(json[0]),
                console.log('fetched_data', json[0])
              );
            } else {
              return dispatch(getDataOptionsFailure());
            }
          })
          .catch(err => dispatch(getDataOptionsFailure(err)));
      };
    };
    export const resetDataOptions = () => {
      return {
        type: FETCH_DATA_OPTIONS_RESET
      };
    };
    function getDataOptions() {
      return {
        type: FETCHING_DATA_OPTIONS,
      };
    }
    function getDataOptionsSuccess(data) {
      return {
        type: FETCH_DATA_OPTIONS_SUCCESS,
        data: data.DATA,
        status: data.STATUS
      };
    }
    function getDataOptionsFailure() {
      return {
        type: FETCH_DATA_OPTIONS_FAILURE,
      };
    }


Solution 1:[1]

You haven't changed the value of refreshing state variable after dispatching the API call. It's set as true in your handleRefresh function but never set back to false.

Use one of the lifecycle methods componentWIllUpdate or componentDidUpdate to check whether the props have changed (API call success or fail), and change the state of your refreshing variable to false.

For eg. in your Search component add this,

componentDidUpdate(prevProps) {
  //Check whether props have changed and isFetching is false
  if (this.props.isFetching !== prevProps.isFetching && !this.props.isFetching) {
    this.setState({refreshing:false});
  }
}

Also check the value of onEndReachedThreshold is 0. Usage is explained here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nishant Nair