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