react + taro + dva to realize pull-up loading

Posted by Jalz on Sun, 23 Jan 2022 22:16:12 +0100

The final code is at the end

Basic structure

The main body adopts the scrollView component of taro and sets a fixed height for it to make its sliding effective. An onScrollToLower event is configured to trigger the bottom event.

Drop down scroll component

import React from "react";
import { ScrollView } from "@tarojs/components"; 
import { LoadMore } from "../loadMore";

 const Test = () => {
   const onScrollToLower = () => {
		//  Drop to event
   }
   return (
   <>
     <ScrollView 
      className="test"  // Set a fixed height
      onScrollToLower={onScrollToLower}
     />
     <LoadMore>
  <>
   )
 }

LoadMore component
Borrow AtLoadMore from taro UI

import React from "react";
import { AtLoadMore } from "taro-ui/lib";
import { View } from "@tarojs/components";
import { useSelector } from "react-redux";
import { RootState } from "@/models/index";
import "./index.scss";

export const LoadMore = () => {
  // Get the data in the model
  const showLoad = useSelector<RootState>((state) => state.smartPanHistory.showLoad);
  const loadingStatus = useSelector<RootState>((state) => state.smartPanHistory.loadingStatus);
  return (
    <View className={`recordloadmore ${showLoad ? "" : "hide"}`}>
      <AtLoadMore status={loadingStatus} moreText='Pull up loading' />
    </View>
  );
};

Realize splicing data

How to splice the newly loaded data behind the previous data can be borrowed from the refresh method of react, and the new data can be spliced behind by using the concat method of array. React will only update the changed part, so it will not refresh the whole page, but only refresh the new data behind.
Set the initial data to an empty array, so that even if it is the first refresh, it can be refreshed by splicing data without adding new variables to distinguish whether it is the first load or the pull-down refresh.

res.items   Equivalent to   []concatres.items

LoadMore control variable analysis

The LoadMore component of taro UI uses status to control three statuses: more (pull-up loading), loading (loading) and noMore (no more). At the same time, the component displays the pull-up loading data and disappears after it is completed. Therefore, a value is required to control its display and hiding. Therefore, we define two variables loadingStatus, showLoad to control the status and display of LoadMore.

  • showLoad logic analysis
    It is easy to think that when the data is loaded to the end, the component displays the status of pull-up loading, then displays loading, and then starts loading data. After loading data, loading ends, and the component disappears.
    Therefore, the appearance of the component is directly related to the disappearance and pull-up to the end, so it should be controlled in the pull-up to the end event.
    Its default value is false. It will be displayed only when the drop-down is triggered.
  • loadingStatus logic analysis
    Pull up loading is displayed by default, and the default value of loadingStatus is more.
    The display time of loading is directly related to the time of interface adjustment. In addition, loading is displayed before interface adjustment, and loading is closed after interface adjustment. Then, judge whether the component display has no more states or continue to restore the default pull-up loading according to whether there is data left. Therefore, you need to change the value of loadingStatus in the model while calling the interface, and add a hadOver variable to judge whether there is any remaining data.
  • hadOver logic analysis
    This variable is defined by the interface. If there is an attribute indicating whether there is residual data returned in the interface, it can be used directly. If there is no attribute, the returned data length can be used for judgment. Here, the returned data length is used.
    So there is the following code:
const onScrollToLower =  async () => {
    console.log("----------I've reached the end---------");
      try {
        await dispatch({
          type: "smartPanHistory/getHistoryList",
          payload: {
            beginDate: startTime, // Query interface parameters
            endDate: endTime, // Query interface parameters
            status: current === 1 ? "2" : current === 2 ? "3" : "", // Query interface parameters
            limit: 20, // Query interface parameters
            showLoad: true, // Display components
        },
        });
      } catch (err) {
        err && Taro.showModal({ content: err?.info, showCancel: false });
      }
  }

Second bottom bug

The above logic seems to be OK, but a bug will occur in actual operation
When the bottom event is triggered, let the component display, and the scroll recording the sliding of the page will be refreshed. Then, the browser will think that you have triggered the bottom event again, so this function will be executed twice, and the interface will bounce up and down and jam.
Therefore, we should judge whether the component LoadMore is displayed or hidden at the beginning of page loading, rather than letting it be displayed or hidden at the bottom.

showLoad logic secondary analysis
In the initial state, there is no data, so we should display the interface without data. When there is a lot of data (beyond the vertical display range of the mobile phone, you need to pull up to see the following data). At this time, we need to pull up. No matter what the status is, it should be displayed. Similarly, if there is only one piece of data or the data does not exceed the mobile phone interface, the component does not need to be pulled up and will not be displayed.

Therefore, the value of showLoad is related to the amount of interface data, so we should update its value according to the data length after requesting the interface. If the interface is not called before the update, its value should be false, that is, the component will not be displayed.

In this way, when the page is loaded for the first time, the data returned through the interface will show or hide LoadMore in advance. When touching the bottom, dom elements will not change, and the experience will be silky, without jamming and execution twice. (when there are more than 3 pieces of data in my page, it will exceed the range. I set it to 3, and it will appear in the request interface code below)

Logical secondary analysis of loadingStatus

The status of loadingStatus is more by default. According to the above analysis, change its status to loading before calling the interface.
Then judge according to the value of hadOver:

  • hadOver == true

    At this time, there is still remaining data that can continue to trigger the pull-up function. Therefore, after calling the interface, set the loadingStatus to more and showLoad to false, and then concat enate the returned array and update it to the model.

  • hadOver == false
    At this time, the subsequent request interface has no data, so set the loadingStatus status to noMore and showLoad to true.

Component disappearance bug

The above logic looks normal, but it doesn't show that there is no more, but the whole component disappears. If you look at the console, you will find that you have called the bottom function again. The reason is that there is no data at this time, but the bottom event is triggered to call the interface, and the data returned by the interface is empty. According to the first analysis of showLoad, showLoad is false and will not be displayed.

Therefore, it is necessary not to trigger the bottom event after the last monitoring of no data, but this is obviously impossible. Another way of thinking is to execute the bottom event, but add a judgment in the bottom event to judge whether to call the interface, so that various internal states of the component will not be changed and the correct state will be displayed. At the same time, there is no need to call the interface repeatedly, and this judgment variable is naturally the value of hadOver in the model.

Query function

I also have a query function to query the data in the current state of the current time period by changing the start and end times and the corresponding tabs. In this case, it is no longer a simple concat enation splicing, but to empty the data and re brush the page, and each tab has a corresponding array, When calling the interface, judge which one to update according to the incoming status value.
When querying, update the corresponding tab array to an empty array, and then refresh the corresponding data in the model to update it to the data returned by the interface instead of directly splicing data. Therefore, an isquery variable is added to judge whether it is a query function.

Here is the complete code:

Bottom event

//  Remaining variables
  const hadOver = useSelector<RootState>((state) => state.smartPanHistory.hadOver);
//  Bottom function
const onScrollToLower =  async (currentTab) => {
    /**
     * In this step, I want to set the drop-down loading under the tabs component of taro (my three tabs), and there is a bug here
     * The end event will also be triggered when switching tabs. Later, it is found that the current of the control tabs is not synchronized, so an event is set before entering
     * check
     */
    if(currentTab !== current) return;
    // Determine whether the interface is dropped by whether the data is left. If the data is left, continue to call for pull-up loading, otherwise it will not be dropped
    if(hadOver) {
      try {
        await dispatch({
          type: "smartPanHistory/getHistoryList",
          payload: {
            beginDate: startTime,
            endDate: endTime,
            status: current === 1 ? "2" : current === 2 ? "3" : "", // Current tabs value
            limit: 4, // Number of requested data
            anchorID: anchorID, // Current pull-up anchor
        },
        });
      } catch (err) {
        err && Taro.showModal({ content: err?.info, showCancel: false });
      }
    }
  }

model data and request interface

// Here, you can initialize the ModelState
  state: {
    allList: [],   //  Tabs0 data storage variable of Scrollview
    triggeredList: [],   //  Tabs1 data storage variable of Scrollview
    invalidList: [],    //  Tabs2 data storage variable of Scrollview
    showLoad: false,  // Whether to display LoadMore components. It is not displayed by default
    loadingStatus: "more",   // The load more component displays the status. By default, it is pulled up and loaded
    hadOver: true,  // Whether there is still data. The default value is true. Execute the calling interface and change it according to the return value after the request
    anchorID: "",  // Pull up data anchor
    isQuery: false // Is it a query
  },
  effects: {
  //  Query data interface
    *getHistoryList({ payload }: Action, { call, put, select }: EffectCommand & EffectThrottle) {
      //  Get the payload parameter
      const { beginDate, endDate, limit, status, anchorID, isQuery } = payload!;
      //  Get the data list corresponding to each tab in the model
      const {allList, triggeredList, invalidList } = yield select<any>(state => state.smartPanHistory);
      // Change the loadMore component status to loading before calling the interface
      yield put({
        type: "updateState",
        payload: {
          loadingStatus: "loading"
        },
      });
      // Start calling interface IF011704 data
      let res:any = [];
      //  If it is passed, it indicates that it is a pull-up refresh, and the anchor point is passed in
      if(anchorID) {
     	//  If status is passed, it means tabs == 1 or tabs == 2. If status is not passed, it defaults to tabs == 0
        if (status) {
          res = yield call(IF011704, {beginDate, endDate, limit, status, anchorID});
        } else {
          res = yield call(IF011704, {beginDate, endDate, limit, anchorID});
        }
      } else {
        if (status) {
          res = yield call(IF011704, {beginDate, endDate, limit, status});
        } else {
          res = yield call(IF011704, {beginDate, endDate, limit});
        }
      }
      //  Gets the length of the interface array
      const resLength = res.items.length;
      //   Object parameters
      let parmerObj = Object.create(null);  // The model data that needs to be updated in the status of no query
      let queryParmer = Object.create(null);  // model data updated during the second query
      let lastAnchorID = "";  //  The anchor point carried by the last data in the interface
      if(status) {
      //  Determine the status to get which tabs to update, and set the corresponding updated key and value values in parmerObj
        if(status === "2") {
          // Triggered
          parmerObj["triggeredList"] = isQuery ? [] : triggeredList.concat(res.items);
          queryParmer["triggeredList"] = res.items;
        } else {
          // Invalid
          parmerObj["invalidList"] = isQuery ? [] : invalidList.concat(res.items);
          queryParmer["invalidList"] = res.items;
        }
      } else {
        // whole
        parmerObj["allList"] = isQuery ? [] : allList.concat(res.items);
        queryParmer["allList"] = res.items;
      }
      //  If there is no data, do nothing. If there is data, get its anchor point
      if (resLength !== 0) {
        lastAnchorID = res.items[res.items.length - 1].anchorID;
      }
      // Set the pull-up and switching tabs parameters  
      //  Update the initialization status of the LoadMore component. Here I use three pieces of data to control. If it exceeds the display, it will not be displayed if it does not exceed the display
      parmerObj.showLoad = queryParmer.showLoad = resLength  >= 3 ? true : false;
      //  Is there any remaining status for the update data
      parmerObj.hadOver = queryParmer.hadOver = resLength >= limit ? true : false;
      //  Update the LoadMore status status. If there is any remaining, it means that the drop-down loading continues to be more. If there is no data, there are no more noMore
      parmerObj.hadOver ? parmerObj.loadingStatus = "more" : parmerObj.loadingStatus = "noMore";
      // Anchor point
      parmerObj.anchorID = lastAnchorID;
      // Update data without query status
      yield put({
        type: "updateState",
        payload: parmerObj
      });
      // When it is a query button, update the empty array to items again to refresh the data to the top
      isQuery && (
        yield put({
          type: "updateState",
          payload: queryParmer
        })
      );
    },

Topics: Javascript Front-end React Mini Program