自定义 React Native 列表组件未正确处理分页

Custom React Native list component is not handling pagination correctly

我有一个名为 TableList 的自定义列表组件。当用户单击箭头时,它应该递增到下一个数据集。但是,当第一次单击任一方向时,组件只会更新页码,不会更新数据。

例如:在第一页时,点击next递增到第二页,但数据保持不变。再次单击,增加页码和数据。它将继续正确分页,直到用户开始单击 previous。然后它会在第一次点击时给出与之前相同的行为,并在后续点击中正常继续。

主要分页功能:

function changePage(direction) {
    if (
      (currentPage === 0 && direction === 'previous') ||
      currentPage * pageSize > data
    ) {
      return null;
    }
    if (direction === 'previous' && currentPage > 0) {
      const newPage = currentPage - 1;
      setDataSlice(data.slice(newPage * pageSize, currentPage * pageSize));
      setCurrentPage(newPage);
    }
    if (direction === 'next') {
      const newPage = currentPage + 1;
      setDataSlice(data.slice(currentPage * pageSize, newPage * pageSize));
      setCurrentPage(newPage);
    }
  }

完整代码:

import {faArrowRight, faArrowLeft} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome';
import {func, number, string, shape} from 'prop-types';
import React, {useState} from 'react';
import {View, Text, FlatList} from 'react-native';

import EtherButton from '../components/EtherButton';
import {useTheme} from '../context/ThemeContext';

PageMenu.propTypes = {
  onNext: func.isRequired,
  onPrev: func.isRequired,
  page: number.isRequired,
};
function PageMenu({onNext, onPrev, page}) {
  const {style, values} = useTheme(getThemedStyles);

  return (
    <View style={style.pageMenuContainer}>
      <EtherButton style={style.arrowButton} onPress={onPrev}>
        <FontAwesomeIcon icon={faArrowLeft} color={values.BGSECOND} size={30} />
      </EtherButton>
      <View>
        <Text style={style.counter}>{page}</Text>
      </View>
      <EtherButton style={style.arrowButton} onPress={onNext}>
        <FontAwesomeIcon
          icon={faArrowRight}
          color={values.BGSECOND}
          size={30}
        />
      </EtherButton>
    </View>
  );
}

export default function TableList({
  data,
  pageSize,
  style: overrides,
  pageView = true,
}) {
  const {style} = useTheme(getThemedStyles);
  const [loadCount, setLoadCount] = useState(pageSize);
  const [currentPage, setCurrentPage] = useState(0);
  const [dataSlice, setDataSlice] = useState(data.slice(0, pageSize));

  ListItem.proptypes = {
    itemData: shape({
      date: string.isRequired,
      name: string.isRequired,
      orderNumber: string.isRequired,
      total: number.isRequired,
    }),
  };
  function ListItem({orderData}) {
    const [mouseHover, setMouseHover] = useState(false);
    const textHighlight = [
      style.valueText,
      mouseHover ? style.textHighlighted : null,
    ];
    return (
      <View
        onMouseOver={() => setMouseHover(true)}
        onMouseLeave={() => setMouseHover(false)}
        style={[style.listRow, mouseHover ? style.rowHighlighted : null]}
      >
        <View style={style.amountCell}>
          <Text style={textHighlight}>{orderData.total} </Text>
        </View>
        <View style={style.nameCell}>
          <Text style={textHighlight}>{orderData.name}</Text>
        </View>
        <View style={style.tokenCell}>
          <Text style={textHighlight}>{orderData.orderNumber}</Text>
        </View>
        <View style={style.dateCell}>
          <Text style={textHighlight}>
            {new Date(orderData.date).toLocaleString()}
          </Text>
        </View>
      </View>
    );
  }

  function loadMore() {
    const newCount = loadCount + pageSize;
    setLoadCount(newCount);
    setDataSlice(data.slice(0, newCount));
  }

  function changePage(direction) {
    if (
      (currentPage === 0 && direction === 'previous') ||
      currentPage * pageSize > data
    ) {
      return null;
    }
    if (direction === 'previous' && currentPage > 0) {
      const newPage = currentPage - 1;
      setDataSlice(data.slice(newPage * pageSize, currentPage * pageSize));
      setCurrentPage(newPage);
    }
    if (direction === 'next') {
      const newPage = currentPage + 1;
      setDataSlice(data.slice(currentPage * pageSize, newPage * pageSize));
      setCurrentPage(newPage);
    }
  }

  return (
    <View style={[style.mainContainer, overrides]}>
      <View style={style.topRow}>
        <View style={style.amountCell}>
          <Text style={style.headerText}>Price</Text>
        </View>
        <View style={style.nameCell}>
          <Text style={style.headerText}>Description</Text>
        </View>
        <View style={style.tokenCell}>
          <Text style={style.headerText}>Order ID</Text>
        </View>
        <View style={style.dateCell}>
          <Text style={style.headerText}>Date</Text>
        </View>
      </View>
      <FlatList
        data={dataSlice}
        key={dataSlice}
        renderItem={({item}) => <ListItem orderData={item} />}
        keyExtractor={(item) => item.orderNumber}
        style={style.flatList}
      />
      <Text style={style.timeZone}>
        Time shown in {new Date().toString().match(/([A-Z]+[-][0-9]+.*)/)[0]}
      </Text>
      <View style={style.bottomBar}>
        {pageView ? (
          <PageMenu
            onPrev={() => changePage('previous')}
            onNext={() => changePage('next')}
            page={currentPage + 1}
          />
        ) : (
          <EtherButton onPress={loadMore} style={style.loadMoreButton}>
            <Text style={style.buttonText}>Load More</Text>
          </EtherButton>
        )}
      </View>
    </View>
  );
}

const getThemedStyles = (theme, fontSize) => ({
  mainContainer: {
    backgroundColor: theme.BGFIRST,
    borderColor: theme.FIRST,
    borderWidth: 2,
    borderRadius: 5,
    overflowX: 'auto',
  },
  topRow: {
    backgroundColor: theme.SECOND,
    height: 40,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-around',
    borderTopLeftRadius: 2,
    borderTopRightRadius: 2,
  },
  headerText: {
    fontFamily: 'NotoSans_Regular',
    fontSize: fontSize.body,
    color: theme.LIGHT,
    alignSelf: 'center',
  },
  flatList: {
    paddingVertical: 20,
  },
  pageMenuContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  counter: {
    fontFamily: 'NotoSans_Regular',
    fontSize: fontSize.body,
    backgroundColor: theme.BGSECOND,
    color: theme.DARK,
    padding: 5,
    borderRadius: 5,
    borderColor: theme.FIRST,
    borderWidth: 2,
  },
  arrowButton: {
    borderColor: theme.SECOND,
  },
  bottomBar: {
    alignItems: 'center',
    backgroundColor: theme.SECOND,
  },
  loadMoreButton: {
    justifyContent: 'center',
    alignItems: 'center',
    height: 40,
    marginVertical: 5,
    width: '15%',
    borderRadius: 5,
    borderColor: theme.FIRST,
    borderWidth: 2,
    backgroundColor: theme.BGSECOND,
  },
  loadMoreText: {
    fontFamily: 'NotoSans_Regular',
    fontSize: fontSize.subheader,
  },
  buttonText: {
    fontFamily: 'NotoSans_Bold',
    fontSize: fontSize.legal,
    color: theme.FIRST,
    textAlign: 'center',
  },
  // Table
  listRow: {
    alignItems: 'center',
    flexDirection: 'row',
    height: 33,
    justifyContent: 'space-around',
  },
  rowHighlighted: {
    backgroundColor: theme.SECOND,
  },
  valueText: {
    alignSelf: 'center',
    fontFamily: 'NotoSans_Regular',
    fontSize: fontSize.legal,
  },
  textHighlighted: {
    color: theme.LIGHT,
  },
  amountCell: {
    width: 80,
    minWidth: 60,
  },
  nameCell: {
    width: 220,
    minWidth: 210,
  },
  tokenCell: {
    width: 200,
    minWidth: 150,
  },
  dateCell: {
    width: 140,
    minWidth: 115,
  },
  //
  timeZone: {
    alignSelf: 'center',
    fontFamily: 'NotoSans_Bold',
    fontSize: fontSize.legal,
    color: theme.DARK,
    marginBottom: 20,
  },
});

您能否尝试将 changePage 放入 useCallback 并提供 dataSlice 作为依赖项?在我看来这像是数据泄露。

无关:如果您使用的是 keyExtractor,则不应使用 key。我会从 FlatList 中删除 key 道具。

我很尴尬花了这么长时间才弄明白,但我找到了答案。我基于数据设置了错误的变量。更改 if (direction === "next") 部分中的 setState 修复了它。这是我将其更改为:

setDataSlice(data.slice(newPage * pageSize, (newPage + 1) * pageSize));