Append Data in 'Flatlist' without refreshing complete 'Flatlist' in react native

Viewed 895

FlatList comes with the option to fetch the next set of data from API when listview reaches the end and we can also use the scrollTo Function to retain the last scroll position of Flatlist but implementing this way is creating reloading fell of FlatList on every next pagination data request the complete listview loads & we scroll to last left position. Anyways if we can implement a feature where existing data in the Flatlist is not refreshed and only new data append to the bottom without changing scroll position in react native will help render the FlastList in a more optimized way. Any help on the suggestion will be appreciated.

4 Answers

Here is what I understood.

You have a FlatList and fetching data from an API.

onEndReached you are fetching some more data, but when you pass this data to the FlatList the whole list refreshes and you lose the scroll position.

I have done something similar but my scroll position is maintained when appending data.

The following is what works for me. For simplicity's sake, I am only specifying the data part.

const [items, setItems] = useState([]);

<FlatList 
    data={items} 
    onEndReached={() => setItems(items.concat(newData))}
/>

The idea is to contact your new data to the existing data array.

Please let me know if this helps.

The FlatList component in React Native has a prop called onEndReached. This prop is called when the user has reached the end of the list. This is an easy way to fetch the next set of data from an API without refreshing the entire list.

The FlatList component also has a prop called scrollToIndex. This prop allows you to scroll to a specific index in the list. This is useful if you want to retain the last scroll position of the FlatList.

You can learn more about the FlatList component in the React Native documentation:

https://reactnative.dev/docs/flatlist

I have implemented it in this way:

I fetch 10 records from database each time when user scrolls down to the bottom and mutate the state accordingly. Basically, it is using limit keyword within a mysql database like limit 0, 10 or limit 10, 10 to fetch data blocks. When a new data block added to the end of the current data, I do not change the position of scrollbar and it does not change the existing data.

I will share with you my code and it is pretty hard to explain every line of it but I think it may give you an idea of what I am talking about.

State

this.state = {
  list_total: 0,
  order_list: [],
  order_detail_list: [],
  modal_visible: false,
  modal_type: null,
  mode: 'date',
  start_date: date.setMonth(date.getMonth() - 36),
  start_date_show: false,
  end_date: new Date(),
  end_date_show: false,
  date_modal: true,
  scan_modal_visible: false,
  ready_to_insert_last_material_type_value: '',
  ready_to_insert_last_status_value: '',
  ready_to_insert_last_sales_representative_value: '',
  last_material_type_value: '',
  last_status_value: '',
  last_sales_representative_value: '',
  ready_to_insert_last_filter_value: 7,
  last_filter_value: 7,
  ready_to_insert_last_sort_value: 6,
  last_sort_value: 6,
  limit: 10,
  offset: 0,
  loading: false,
  stop: false,
  handlingMore : false,
  refreshing: false
};

FlatList

<FlatList
  extraData={this.state}
  data={this.state.order_list}
  keyExtractor={(item, index) => index.toString()}
  keyboardShouldPersistTaps="always"
  initialNumToRender={10} // Reduce initial render amount
  maxToRenderPerBatch={5} // Reduce number in each render batch
  updateCellsBatchingPeriod={100} // Increase time between renders
  onMomentumScrollBegin = {() => {this.onEndReachedCalledDuringMomentum = false;}}
  onEndReached={() => {
    if (!this.onEndReachedCalledDuringMomentum && !this.state.handlingMore) 
    {
      this.setState({
        handlingMore: true
      }, () => {
        this.handleLoadMore();
                                                  
        this.onEndReachedCalledDuringMomentum = true;
      });
    }
  }}
  onEndReachedThreshold = {0.5}
  ListFooterComponent={() =>
    !this.state.loading ? null
    : (this.state.stop ? null : <ActivityIndicator size="large" animating />
  )}
  renderItem={({item}) => <OrderListItem item={item} order_detail_list={this.state.order_detail_list} />}
  refreshing={this.state.refreshing} 
  onRefresh={this.handleRefresh}
/>

handleRefresh: Method when you refresh the screen, it gets the first 10 rows from db (if it has 10 records at least, it may be less)

handleLoadMore: Method to get next data block (if exists, it may be 10 rows or less)

handleRefresh = (isApplyButton) => {
  if (isApplyButton)
    this.props.setFullLoading(true);

    this.setState({
      offset: 0,
      stop: false,
      refreshing: true,
      handlingMore: false,
    }, () => {
      let obj = {
        company_uid: this.props.company_uid,
        user_uid: this.props.user_uid,
        sort_filter: this.props.sort_filter_orders,
        limit: this.state.offset + ',' + this.state.limit
    };

    Promise.all([this.props.getOrderList(obj),this.props.getOrderDetailList(obj)])
      .then(resultArray => {
        this.setState(state => ({
          list_total: resultArray[0].length,
          order_list: resultArray[0],
          order_detail_list: resultArray[1],
          refreshing: false
        }));
        if (isApplyButton) {
          if (this.props.full_loading) {
            this.props.setFullLoading(false);
          }
        }
      })
      .catch(error => {
        WToast.show({data: lang.ERROR});
        console.log(error);
      });
    });
};

_getOrderList = async () => {
  this.setState({
    loading: true
  });
  let obj = {
    company_uid: this.props.company_uid,
    user_uid: this.props.user_uid,
    sort_filter: this.props.sort_filter_orders,
    limit: this.state.offset + ',' + this.state.limit
  };

  Promise.all([this.props.getOrderList(obj),this.props.getOrderDetailList(obj)])
    .then(resultArray => {
      this.setState(state => ({
        order_list: [...state.order_list, ...resultArray[0]],
        order_detail_list: resultArray[1],
        loading: false,
        stop: resultArray[0].length < this.state.limit
      }), () => 
        this.setState({list_total: this.state.order_list.length, handlingMore: false}));
        if (this.props.full_loading) {
          this.props.setFullLoading(false);
        }
      })
      .catch(error => {
        WToast.show({data: lang.ERROR});
        console.log(error);
    });
};

handleLoadMore = () => {
  if (this.state.stop)
    return;

  this.setState(state => ({offset: state.offset + 10}), () =>  this._getOrderList());
};

Reducer action

export const getOrderList = (obj) => {
    return (dispatch) => {
        const promise = new Promise((resolve, reject) => {
            const post_data = {
                function: 'Get_fetch_all',
                token: SEND_POST_TOKEN,
                sql_query: 'SELECT o.uid, o.date, o.document_no, IFNULL(o.description, \'\') description, CASE o.status WHEN 0 THEN \'Açık\' WHEN 1 THEN \'Kapalı\' WHEN 2 THEN \'İptal\' WHEN 3 THEN \'İstenildi\' WHEN 4 THEN \'Teslim Edildi\' ELSE \'İade Edildi\' END status, CASE o.material_type WHEN 0 THEN \'Kartela\' WHEN 1 THEN \'Numune\' ELSE \'Model\' END material_type\n' +
                    'FROM orders o\n' +
                    'LEFT JOIN (SELECT od.company_uid, od.uid, od.order_uid, od.no, od.material_uid, od.quantity, od.unit,\n' +
                    '                  CASE WHEN material_type = 0 THEN sc.code\n' +
                    '                       WHEN material_type = 1 THEN s.code\n' +
                    '                       WHEN material_type = 2 THEN m.code END material_code\n' +
                    '            FROM order_details od\n' +
                    '                     INNER JOIN orders o ON (od.company_uid = o.company_uid AND od.order_uid = o.uid)\n' +
                    '                     LEFT JOIN swatch_cards sc ON (od.company_uid = sc.company_uid AND od.material_uid = sc.uid)\n' +
                    '                     LEFT JOIN users u ON (o.company_uid = u.company_uid AND o.sales_representative_uid = u.uid)\n' +
                    '                     LEFT JOIN (SELECT s.company_uid, s.uid, s.code, v.variant FROM samples s INNER JOIN variants v ON (s.company_uid = v.company_uid AND s.variant_uid = v.uid)) s ON (od.company_uid = s.company_uid AND od.material_uid = s.uid)\n' +
                    '                     LEFT JOIN (SELECT m.company_uid, m.uid, m.code, v.variant FROM models m INNER JOIN variants v ON (m.company_uid = v.company_uid AND m.variant_uid = v.uid)) m ON (od.company_uid = m.company_uid AND od.material_uid = m.uid)\n' +
                    ') m ON (o.company_uid = m.company_uid AND o.uid = m.order_uid)\n' +
                    'WHERE o.company_uid = :company_uid AND (DATE(o.date) BETWEEN :filter_start_date AND :filter_end_date) AND\n' +
                    'CASE WHEN (o.material_type = 0 AND (m.material_code = :material_code OR :material_code = \'\')) OR (o.material_type = 1 AND (m.material_code = :material_code OR :material_code = \'\')) OR (o.material_type = 2 AND (m.material_code = :material_code OR :material_code = \'\')) THEN 1 ELSE 0 END = 1\n' +

                    'AND (CAST(o.material_type AS CHAR) IN (\n' +
                    'SELECT CASE WHEN swatch_card_auth = 1 THEN \'0\' ELSE \'\' END\n' +
                    'FROM users WHERE uid = :user_uid\n' +
                    ') OR CAST(o.material_type AS CHAR) IN (\n' +
                    'SELECT CASE WHEN sample_auth = 1 THEN \'1\' ELSE \'\' END\n' +
                    'FROM users WHERE uid = :user_uid\n' +
                    ') OR CAST(o.material_type AS CHAR) IN (\n' +
                    'SELECT CASE WHEN model_auth = 1 THEN \'2\' ELSE \'\' END\n' +
                    'FROM users WHERE uid = :user_uid\n' +
                    ') OR o.user_uid = :user_uid OR o.sales_representative_uid = :user_uid)',
                fetch_json: {
                    company_uid: obj.company_uid,

                    user_uid: obj.user_uid,

                    filter_start_date: obj.sort_filter.filter_start_date,
                    filter_end_date: obj.sort_filter.filter_end_date,
                    material_code: obj.sort_filter.material_code
                }
            };

            switch (obj.sort_filter.filter) {
                case 8:
                    post_data.fetch_json.material_type = obj.sort_filter.filter_obj.materialTypeValue;
                    post_data.sql_query += ' AND (o.material_type = :material_type OR :material_type = \'\')';
                    post_data.fetch_json.status = obj.sort_filter.filter_obj.statusValue;
                    post_data.sql_query += ' AND (o.status = :status OR :status = \'\')';
                    post_data.fetch_json.sales_representative_uid = obj.sort_filter.filter_obj.salesRepresentativeValue;
                    post_data.sql_query += ' AND (o.sales_representative_uid = :sales_representative_uid OR :sales_representative_uid = \'\')';
                    break;
            }

            post_data.sql_query += ' GROUP BY o.uid, o.date, o.document_no, o.material_type, o.created_date';

            switch (obj.sort_filter.sort) {
                case 1:
                    post_data.sql_query += ' ORDER BY o.material_type ASC, o.created_date ASC, o.document_no ASC';
                    break;

                case 2:
                    post_data.sql_query += ' ORDER BY o.material_type DESC, o.created_date DESC, o.document_no DESC';
                    break;

                case 3:
                    post_data.sql_query += ' ORDER BY o.document_no ASC, o.created_date ASC';
                    break;

                case 4:
                    post_data.sql_query += ' ORDER BY o.document_no DESC, o.created_date DESC';
                    break;

                case 5:
                    post_data.sql_query += ' ORDER BY o.created_date ASC, o.document_no ASC';
                    break;

                case 6:
                    post_data.sql_query += ' ORDER BY o.created_date DESC, o.document_no DESC';
                    break;
            }

            post_data.sql_query += ' LIMIT '  + obj.limit;

            axios.post(SERVICE_API_URL, post_data, postHeaderUserAgent)
                .then((response) => {
                    resolve(response.data.fetch_all);
                })
                .catch((error) => {
                    reject(error);
                });
        });
        return promise
            .then((response) => {
                return response;
            }).catch(error => {
                throw(new Error(error));
            });
    }
};

Result https://imgur.com/a/8qM5PQN

Pagination example:-

class FidsListview extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isLoading: false,
      studentData: [],
      currentPage: 0,
      lastPage: 0,
    };

    this.getChildrenApi = this.getChildrenApi.bind(this);
  }

  componentDidMount() {
    this.getChildrenApi();
  }

  getChildrenApi() {
    let currentPage = this.state.currentPage + 20;
    fetch(
      `https://fids.myairports.com.my/api/flights/get-flight-departures-cache?skip=${currentPage}&take=${
        currentPage + 20
      }&terminal=KLIA`
    )
      .then((result) => result.json())
      .then((res) => {
        if (
          res.flightStatuses != null &&
          res.flightStatuses != undefined &&
          res.flightStatuses.length > 0
        ) {
          this.setState({
            studentData: res.flightStatuses,
          });
        } else {
          this.setState({ studentData: [] });
        }
      });
  }

  lodeMoreData() {
    let nextPage = this.state.currentPage + 20;

    fetch(
      `http://paginationapi.com?skip=${nextPage}&take=${
        nextPage + 20
      }&terminal=KLIA`
    )
      .then((result) => result.json())
      .then((res) => {
        if (
          res.flightStatuses != null &&
          res.flightStatuses != undefined &&
          res.flightStatuses.length > 0
        ) {
          this.setState({
            studentData: [...this.state.studentData, ...res.flightStatuses],
            currentPage: nextPage,
          });
        } else {
          this.setState({ studentData: [] });
        }
      });
  }

  render() {
    return (
      <View style={{}}>
        <FlatList
          style={{ backgroundColor: "#FFF" }}
          showsVerticalScrollIndicator={false}
          data={this.state.studentData}
          numColumns={3}
          ListFooterComponent={() => <View style={{ marginBottom: 300 }} />}
          extraData={this.state.studentData}
          renderItem={({ item, index }) => (
            <View style={{ height: 100, backgroundColor: "#EEE" }}>
              <Text>{item.afsKey}</Text>
            </View>
          )}
         
        />
        {this.state.isLoading && (
          <View
            style={{
              width: "100%",
              height: "100%",
              alignItems: "center",
              justifyContent: "center",
              position: "absolute",
            }}
          >
            <ActivityIndicator size="large" color={"white"} />
          </View>
        )}
      </View>
    );
  }
}

export def

ault FidsListview;

Related