bvaughn / react-window

React components for efficiently rendering large lists and tabular data
https://react-window.now.sh/
MIT License
15.49k stars 778 forks source link

InfiniteLoader not working when api data fetch limit is equal to columnCount in FixedSizeGrid. #764

Open ayushbharadva25 opened 1 month ago

ayushbharadva25 commented 1 month ago

Hi @bvaughn, I am using react-window-infinite-loader along with FixedSizeGrid it is working properly, except in the scenario where the limit of data in API call is equal to column count. I am trying to find the reason but couldn't found yet. If anyone knows the reason then help..

Below is the code snippets:

  1. VirtualizedInfiniteList Component :
import { PropTypes } from 'prop-types';
import { FixedSizeGrid, areEqual } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import AutoSizer from 'react-virtualized-auto-sizer';
import './store_projects.scss';
import { StoreMediaQueries } from '../../constants';
import useMediaQuery from '../../hooks/useMediaQuery';

const { mediaQueries } = StoreMediaQueries;

const getIndexFromGridPosition = (col, row, columnCount) => (row * columnCount) + col;

const GridCell = memo(({
  data: { items, gridColumnCount, children }, columnIndex, rowIndex, style,
}) => {
  const index = getIndexFromGridPosition(columnIndex, rowIndex, gridColumnCount);

  if (!items || items.length === 0 || index >= items.length) {
    return null;
  }

  return (
    <div className="store-project-card-wrapper" style={style}>
      {children(items[index])}
    </div>
  );
}, areEqual);

const VirtualizedInfiniteList = memo(({
  items,
  hasMore,
  loadMoreItems,
  threshold,
  children,
}) => {
  const activeMediaQuery = useMediaQuery(mediaQueries);
  const { gridColumnCount = 1, gridRowHeight } = activeMediaQuery?.mediaQuery ?? {};

  const itemCount = useMemo(() => (hasMore ? items.length + 1 : items.length), [hasMore, items.length]);

  const isItemLoaded = (index) => !hasMore || index < items.length;

  const handleItemsRender = useCallback(({
    visibleRowStartIndex, visibleRowStopIndex, visibleColumnStopIndex, onItemsRendered,
  }) => {
    console.log('visibleRowStartIndex :', visibleRowStartIndex, '\nvisibleRowStopIndex :', visibleRowStopIndex, '\nvisibleColumnStopIndex :', visibleColumnStopIndex);

    console.log('visibleStartIndex :', visibleRowStartIndex * (visibleColumnStopIndex + 1), '\nvisibleStopIndex :', visibleRowStopIndex * (visibleColumnStopIndex + 1));
    onItemsRendered({
      visibleStartIndex: visibleRowStartIndex * (visibleColumnStopIndex + 1),
      visibleStopIndex: visibleRowStopIndex * (visibleColumnStopIndex + 1),
    });
  }, []);

  return (
    <AutoSizer>
      {({ height, width }) => (
        <InfiniteLoader
          isItemLoaded={isItemLoaded}
          itemCount={itemCount}
          loadMoreItems={loadMoreItems}
          threshold={threshold}
        >
          {({ onItemsRendered, ref }) => (
            <FixedSizeGrid
              className="virtualized-grid-wrapper"
              columnCount={gridColumnCount}
              columnWidth={width / gridColumnCount}
              height={height}
              itemData={{ items, gridColumnCount, children }}
              ref={ref}
              rowCount={Math.ceil(items.length / gridColumnCount)}
              rowHeight={gridRowHeight}
              width={width}
              onItemsRendered={(props) => handleItemsRender({ ...props, onItemsRendered })}
            >
              {GridCell}
            </FixedSizeGrid>
          )}
        </InfiniteLoader>
      )}
    </AutoSizer>
  );
});

VirtualizedInfiniteList.propTypes = {
  items: PropTypes.shape([]).isRequired,
  hasMore: PropTypes.bool,
  loadMoreItems: PropTypes.func,
  threshold: PropTypes.number,
  children: PropTypes.func.isRequired,
};

VirtualizedInfiniteList.defaultProps = {
  hasMore: false,
  loadMoreItems: () => {},
  threshold: 5,
};

GridCell.propTypes = {
  data: PropTypes.shape({
    items: PropTypes.shape([]),
    gridColumnCount: PropTypes.number,
    children: PropTypes.func,
  }).isRequired,
  columnIndex: PropTypes.number.isRequired,
  rowIndex: PropTypes.number.isRequired,
  style: PropTypes.shape({}).isRequired,
};

export default VirtualizedInfiniteList;
  1. loadMoreItems function :
  const [getStoreProjects, {
    fetchMore,
  }] = useLazyQuery(getProjects, {
    variables: { limit: allProjectsLimit, offset: 0 },
    onCompleted: (res) => {
      const { filterProjectsForAdmin } = res ?? {};
      const projects = filterProjectsForAdmin.length ? filterProjectsForAdmin : [];
      const hasMoreProjects = filterProjectsForAdmin.length >= allProjectsLimit;

      setProject(() => ({
        list: projects,
        hasMore: hasMoreProjects,
        isLoading: false,
      }));
    },
    onError: (error) => {
      showToast({ description: error.message, title: 'Error', type: 'error' });
      setProject(() => ({
        list: [],
        hasMore: false,
        isLoading: false,
      }));
    },
    fetchPolicy: 'network-only',
  });

    const loadMoreItems = useCallback(() => {
    if (!isLoading && hasMore) {
      setProject((prev) => ({ ...prev, isLoading: true }));
      fetchMore({
        variables: {
          limit: allProjectsLimit,
          offset: list.length,
        },
        updateQuery: (prev, { fetchMoreResult: { filterProjectsForAdmin: storeProjects } }) => {
          if (!storeProjects.length) {
            return prev;
          }

          setProject((prevProjects) => ({
            list: [...prevProjects.list, ...storeProjects],
            hasMore: storeProjects.length >= allProjectsLimit,
            isLoading: false,
          }));
          return null;
        },
      });
    }
  }, [fetchMore, hasMore, isLoading, list.length]);

here, the issue occurs when the value of allProjectsLimit and gridColumnCount both have same value. the issue is described below with example.

issue : I am fetching data from an API, it's an infinite scroll api so I need to pass limit in every api call. now for example, suppose there are 20 data items. and the limit of data items in a single API call is 5. and gridColumnCount is also 5

• ideal behaviour : there should be 5 API calls and all the data items should be displayed. • current behaviour : only one api call is made and only 5 data items are displayed. there is no further loadMoreItems call.

Any help regarding above issue would be appreciated, Thanks.