List pages are very common in mobile development. In React Native, FlatList or SectionList components are used to implement list views. In many cases, it is essential for list components to implement pull-down refreshes and pull-up loads when a large amount of data needs to be loaded and displayed on list pages.

This article encapsulates a RefreshListView based on the FlatList that supports pull-down refresh and pull-up loading. After encapsulating the original FlatList, it is convenient to invoke the pull-down refresh and pull-down refresh.

The implementation of the pull-down refresh is quite simple, and we use the properties of the FlatList itself

OnRefresh – When this option is set, a standard RefreshControl is added to the head of the list for “drop-down refresh” functionality. You also need to set the refreshing property correctly.

Refreshing — bool, which controls the display and hiding of refresh controls. Set to false after the refresh is complete.

By setting these two properties, we can refresh the FlatList header. The controls use the default style, and Android and iOS use their respective system components to display.

The key is to load more by pull-up, which is not available in React Native’s list component, so we need to implement it ourselves. For pull-up loads, we usually have several states. Here I create a refreshState. js file to store the pull-up loads:

export default {
  Idle: 'Idle'// Initial state, no refresh condition CanLoadMore:'CanLoadMore', // add more data to the list.'Refreshing', // Updating NoMoreData:'NoMoreData', // No more data'Failure'// Refresh failed}Copy the code

The RefreshFooter component is packaged according to these states and displays different contents according to the different states. Without further ado, the code:

import React, {Component} from 'react';
import {View, Text, ActivityIndicator, StyleSheet, TouchableOpacity} from 'react-native';
import RefreshState from './RefreshState';
import PropTypes from 'prop-types';

exportdefault class RefreshFooter extends Component { static propTypes = { onLoadMore: PropTypes. Func, // Method of loading more data onRetryLoading: PropTypes. Func, // method of reloading}; static defaultProps = { footerRefreshingText:"Trying to load.",
    footerLoadMoreText: "Pull up to load more",
    footerFailureText: "Click reload",
    footerNoMoreDataText: "All loaded"
  };
  
  render() {
    let {state} = this.props;
    let footer = null;
    switch (state) {
      caseRefreshState.Idle: // Null in Idle case, tail components are not displayedbreak;
      caseRefreshing: // Display a loadingView footer = <View style={styles.loadingView}> <ActivityIndicator size="small"/>
            <Text style={styles.refreshingText}>{this.props.footerRefreshingText}</Text>
          </View>;
        break;
      caseRefreshState.CanLoadMore: Footer = <View style={styles.loadingView}> <Text style={styles.footerText}>{this.props.footerLoadMoreText}</Text> </View>;break;
      caseRefreshState.NoMoreData: // Display text with no more data, Footer = <View style={styles.loadingView}> <Text style={styles.footerText}>{this.props.footerNoMoreDataText}</Text> </View>;break;
      caseRefreshState.Failure: // Make a clickable component using TouchableOpacity, External call onRetryLoading reload the footer = < TouchableOpacity style = {styles. LoadingView} onPress = {() = > {this. Props. OnRetryLoading && this.props.onRetryLoading(); }}> <Text style={styles.footerText}>{this.props.footerFailureText}</Text> </TouchableOpacity>;break;
    }
    return footer;
  }
}

const styles = StyleSheet.create({
  loadingView: {
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 15,
  },
  refreshingText: {
    fontSize: 12,
    color: "# 666666",
    paddingLeft: 10,
  },
  footerText: {
    fontSize: 12,
    color: "# 666666"}});Copy the code

Note that propTypes is the method we defined for external calls to the RefreshFooter component, the method type needs to be specified using propTypes, and facebook’s prop-types dependency library needs to be installed, preferably using YARN Add prop-types. Not prone to mistakes. This is used for type checking at runtime, and you can click here to learn more.

In defaultProps we define the default text content in several different states, which can be modified by passing values externally.

The next step is to implement the RefreshListView. The first thing that should be made clear is that the RefreshListView should have header refresh and tail refresh invocation methods, and the data invocation methods should be implemented externally. Define two methods like RefreshFooter:

Static propTypes = {onHeaderRefresh: propTypes. Func, // A pull-down refresh method for external calls to onFooterRefresh: Proptypes.func, // pull-up loaded methods for external calls};Copy the code

We need to add a bool isHeaderRefreshing to the refreshing property of the header using the FlatList feature. We also define an isFooterRefreshing to determine the refresh status of the tail component. Define footerState to set the state of the current tail component as the value of RefreshFooter.

constructor(props) {
    super(props);
    this.state = {
      isHeaderRefreshing: false, // Whether the header is refreshing isFooterRefreshing:falseFooterState: RefreshState.Idle, // The current state of the tail, default is Idle, no control is displayed}}Copy the code

The render function looks like this:

render() {
    return( <FlatList {... this.props} onRefresh={()=>{ this.beginHeaderRefresh() }} refreshing={this.state.isHeaderRefreshing} onEndReached={() => {this.beginfooterRefresh ()}} onEndReachedThreshold={0.1} // The value is 0.1 (0 and 1 are not included). ListFooterComponent={this._renderFooter} />)} _renderFooter = () => {ListFooterComponent={this._renderFooter} />)} _renderFooter = () => {return (
      <RefreshFooter
        state={this.state.footerState}
        onRetryLoading={()=>{
          this.beginFooterRefresh()
        }}
      />
    )
  };
Copy the code

You can see that there are beginHeaderRefresh and beginFooterRefresh methods in the code above, which are used to invoke the refresh, but there are some logical situations to determine before you refresh. For example, the header and tail can not be refreshed at the same time, otherwise the data processing results may be affected, and to prevent repeated refresh operations while refreshing, these are important considerations. Here I comment it in detail in the code:

/// Start the refresh drop-downbeginHeaderRefresh() {
  if(this.shouldStartHeaderRefreshing()) { this.startHeaderRefreshing(); }} /// Start pull-up loading morebeginFooterRefresh() {
  if(this.shouldStartFooterRefreshing()) { this.startFooterRefreshing(); }} /*** * Whether a pull-down refresh is currently available * @returns {Boolean} ** Returns if a pull-up load is being performed at the end of the listfalse* Returns if the list header is already being refreshedfalse* /shouldStartHeaderRefreshing() {
  if (this.state.footerState === RefreshState.refreshing ||
    this.state.isHeaderRefreshing ||
    this.state.isFooterRefreshing) {
    return false;
  }
  return true; } /*** * Whether the current load can be pulled up more * @returns {Boolean} ** If the bottom is already refreshing, returnfalse* If the bottom state is no more data, returnfalse* Returns if the header is refreshingfalse* Returns if the list data is emptyfalseIn the initial state, the following table is empty, so you definitely don't need to pull up to load more, you should do a drop-down refreshshouldStartFooterRefreshing() {
  if (this.state.footerState === RefreshState.refreshing ||
    this.state.footerState === RefreshState.NoMoreData ||
    this.props.data.length === 0 ||
    this.state.isHeaderRefreshing ||
    this.state.isFooterRefreshing) {
    return false;
  }
  return true;
}
Copy the code

The logic of startHeaderRefreshing and startFooterRefreshing is as follows:

/// pull down refresh, set the refresh state and then call refresh method, so that the page can display the loading UI, pay attention to heresetThe State of writingstartHeaderRefreshing() {
  this.setState(
    {
      isHeaderRefreshing: true}, () => { this.props.onHeaderRefresh && this.props.onHeaderRefresh(); }); } /// Pull up load more, change the bottom refresh state to refresh, then call refresh method, the page can display the UI loading, notice heresetThe State of writingstartFooterRefreshing() {
  this.setState(
    {
      footerState: RefreshState.Refreshing,
      isFooterRefreshing: true}, () => { this.props.onFooterRefresh && this.props.onFooterRefresh(); }); }Copy the code

Before we refresh, we need to display the header or tail components and then call the external data interface methods. The nice thing about setState here is that the methods in the arrow function are not called until the values in the state are updated, so there’s a strict order, If you take this. Props. OnFooterRefresh && enclosing props. OnFooterRefresh written on the outside of the setState (), on the UI we could see the efforts of the loading of the head or the tail load, interface method is invoked.

Finally, after the refresh, we also need to call the stop refresh method so that the header or tail component is not displayed, otherwise the loading may be considered as a bug. Here’s how to stop the refresh:

* @param footerState ** If the refresh is complete and the current list data source is empty, the tail components will not be displayed. * We do this because we usually display a blank page when the list has no data"There's no more data."*/ endRefreshing(footerState: RefreshState) {let footerRefreshState = footerState;
  if (this.props.data.length === 0) {
    footerRefreshState = RefreshState.Idle;
  }
  this.setState({
    footerState: footerRefreshState,
    isHeaderRefreshing: false,
    isFooterRefreshing: false})}Copy the code

A trailing component state parameter is passed here to update the styling of the trailing component. At the same time, a judgment is made on the data source data. If it is empty, it indicates that there is no data at present and the blank page can be displayed, so there is no need to display the tail component.

The following is the effect picture of douban movie page page-loading using RefreshListView:

Full Demo address: github.com/mrarronz/re… Thanks for reading!