Home > Net >  Custom React Native list component is not handling pagination correctly
Custom React Native list component is not handling pagination correctly

Time:04-10

I have a custom list component called TableList. When a user clicks on an arrow it should increment to the next data set. However, when the first click in either direction is made, the component only updates the page number, not the data.

For example: when on page one, clicking next increments to page two, but the data remains the same. Clicking again, increments both the page number and the data. It will continue to paginate correctly, until the user starts clicking previous. Then it will give the same behavior as before for the first click, and continue normally for subsequent clicks.

The primary pagination function:

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);
    }
  }

Full code:

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
        onm ouseOver={() => setMouseHover(true)}
        onm ouseLeave={() => 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,
  },
});

CodePudding user response:

Could you please try putting changePage into a useCallback and providing dataSlice as a dependency? It looks like a data leak to me.

Unrelated: you shouldn't use key if you are using the keyExtractor. I would remove the key prop from the FlatList.

CodePudding user response:

I'm embarrassed that it took me this long to figure it out, but I found the answer. I was basing the data set off the wrong variables. Changing the setState in the if (direction === "next") section fixed it. Here's what I changed it to:

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