FixedSizeGrid does not activate the loadMoreItems function within the InfiniteLoader component in react-window

Hey there! I'm diving into the world of React JS and looking to create a dynamic product list using react-window. I've got the hang of implementing a fixed-size list with InfiniteLoader, which allows me to successfully make API calls and add new data to the productList as needed. However, my goal now is to display the products in a gridview format with 3 products per row. When trying to achieve this by integrating FixedSizeGrid within InfiniteLoader, I encountered an issue where API calls were not being made while scrolling through the list. Are there any solutions or alternative approaches that could help me overcome this challenge?

import React, { useEffect, useState } from "react";
import Image from 'next/image'
import { FixedSizeList as List, FixedSizeGrid as Grid  } from 'react-window'
import InfiniteLoader from 'react-window-infinite-loader'
import Paper from '@mui/material/Paper';
import { styled } from '@mui/material/styles';
import axios from 'axios';
import { Box, Stack, Typography } from '@mui/material';

// Custom imports
import { poppinsBold } from '../../shared/appfonts';

// Variables used for infinite virtualized list
const PAGE_SIZE = 20;
const TOTAL_PRODUCTS = 1000;
const LOAD_THRESHOLD = 200;
const itemSize = 200; // Fixed item size for 3 items per row
const columnCount = 3; // Number of columns (items per row)

const ProductList = () => {
    const [products, setProducts] = useState([]); // State to maintain product list data
    const [isLoading, setIsLoading] = useState(false); // State to maintain loading state while fetching products from the server
    const [currentPage, setCurrentPage] = useState(1); // State to maintain the current page record

    // Custom Title MUI component for UI
    const TitleTypography = styled(Typography)({
        fontFamily: poppinsBold.style.fontFamily,
        color: 'black',
    });

    // Custom Medium MUI component for UI
    const MediumTypography = styled(Typography)({
        fontFamily: poppinsBold.style.fontFamily,
        color: 'black',
    });

    // Function used to fetch products from the server
    const fetchProducts = async (pageNumber = currentPage) => {
        setIsLoading(true);
        try {
            const response = await axios.get(
                `http://localhost:5000/products?_page=${pageNumber}&_limit=${PAGE_SIZE}`
            );
            const newProducts = response.data;
            setProducts([...products, ...newProducts]);
            setCurrentPage(currentPage + 1);
        } catch (error) {
            console.error('Error fetching products:', error);
        } finally {
            setIsLoading(false);
        }
    };

    // Function used to load more product items to the infinite list
    const loadMoreItems = () => {
        console.log('load more items called')
        if (!isLoading) {
            console.log("currentPage", currentPage);
            if (products.length < TOTAL_PRODUCTS) {
                fetchProducts(currentPage);
            }
        }
    };

    useEffect(() => {
        fetchProducts();
    }, []); // Fetch initial data

    const renderItem = ({ columnIndex, rowIndex, style }) => {
        const index = rowIndex * columnCount + columnIndex;
        const product = products[index];

        return (
            <div style={style} key={product?.id}>
                <Paper sx={{ m: 3 }} elevation={3}>
                    <Stack display={'flex'} direction={'row'}>
                        <Image
                            width={120}
                            height={150}
                            src={
                                'https://images.pexels.com/photos/10863290/pexels-photo-10863290.jpeg?auto=compress&cs=tinysrgb&w=600'
                            }
                            alt="Alternate image"
                            style={{ objectFit: 'fill' }}
                        />
                        <Box
                            display={'flex'}
                            alignItems={'center'}
                            flexDirection={'column'}
                            justifyContent={'center'}
                            width={'100%'}
                        >
                            <TitleTypography>{product?.name}</TitleTypography>
                            <MediumTypography>${product?.price}</MediumTypography>
                        </Box>
                    </Stack>
                </Paper>
            </div>
        );
    };

    return (
        <Box>
            <InfiniteLoader
                isItemLoaded={() => isLoading}
                itemCount={TOTAL_PRODUCTS}
                loadMoreItems={loadMoreItems}
            >
                {({ onItemsRendered, ref }) => (
                    <Grid
                        height={window.innerHeight}
                        width={window.innerWidth}
                        columnCount={columnCount}
                        columnWidth={window.innerWidth / columnCount}
                        rowCount={Math.ceil(products.length / columnCount)}
                        rowHeight={200}
                        onItemsRendered={onItemsRendered}
                        ref={ref}
                    >
                        {renderItem}
                    </Grid>
                )}
            </InfiniteLoader>
        </Box>
    )
}

export default ProductList;

Answer №1

There seems to be a bug in the default example for onItemsRendered={onItemsRendered}

I was able to resolve the issue by changing this prop to

onItemsRendered={gridProps => { onItemsRendered({ overscanStartIndex: gridProps.overscanRowStartIndex * NUM_COLUMNS, overscanStopIndex: gridProps.overscanRowStopIndex * NUM_COLUMNS, visibleStartIndex: gridProps.visibleRowStartIndex * NUM_COLUMNS, visibleStopIndex: gridProps.visibleRowStopIndex * NUM_COLUMNS }); }}

import { FixedSizeGrid as Grid } from "react-window";
import InfiniteLoader from "react-window-infinite-loader";

const LOADING = 1;
const LOADED = 2;
const NUM_COLUMNS = 3;
let itemStatusMap = {};

const isItemLoaded = index => !!itemStatusMap[index];
const loadMoreItems = (startIndex, stopIndex) => {
  for (let index = startIndex; index <= stopIndex; index++) {
    itemStatusMap[index] = LOADING;
  }
  return new Promise(resolve =>
    setTimeout(() => {
      for (let index = startIndex; index <= stopIndex; index++) {
        itemStatusMap[index] = LOADED;
      }
      resolve();
    }, 2500)
  );
};

class Cell extends PureComponent {
  render() {
    const { columnIndex, rowIndex, style } = this.props;
    let label;
    const itemIndex = rowIndex * NUM_COLUMNS + columnIndex;
    if (itemStatusMap[itemIndex] === LOADED) {
      label = `Cell (${rowIndex}, ${columnIndex})`;
    } else {
      label = "Loading...";
    }
    return (
      <div className="ListItem" style={style}>
        {label}
      </div>
    );
  }
}

export default function App() {
  return (
    <Fragment>
      <p className="Note">
        This demonstration application simulates loading remote data with a delay of 2.5 seconds. While rows are in the process of 'loading', they will display a 'Loading...' message. Once the data has been successfully 'loaded', the row number will be displayed. Start scrolling through the list to trigger automatic data loading.
      </p>
      <InfiniteLoader
        isItemLoaded={isItemLoaded}
        itemCount={1000}
        loadMoreItems={loadMoreItems}
      >
        {({ onItemsRendered, ref }) => (
          <Grid
            className="List"
            columnCount={NUM_COLUMNS}
            columnWidth={100}
            height={150}
            rowCount={1000}
            rowHeight={35}
            onItemsRendered={gridProps => {
              onItemsRendered({
                overscanStartIndex:
                  gridProps.overscanRowStartIndex * NUM_COLUMNS,
                overscanStopIndex: gridProps.overscanRowStopIndex * NUM_COLUMNS,
                visibleStartIndex: gridProps.visibleRowStartIndex * NUM_COLUMNS,
                visibleStopIndex: gridProps.visibleRowStopIndex * NUM_COLUMNS
              });
            }}
            ref={ref}
            width={300}
          >
            {Cell}
          </Grid>
        )}
      </InfiniteLoader>
      <p className="Note">
        For more information, please refer to the documentation:
        <br />
        <a href="https://github.com/bvaughn/react-window-infinite-loader#documentation">
          github.com/bvaughn/react-window-infinite-loader
        </a>
      </p>
    </Fragment>
  );
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to manually close the modal in Next.js using bootstrap 5

Incorporating Bootstrap 5.2 modals into my Next.js application has been smooth sailing so far. However, I've encountered an issue with closing the modal window after a successful backend request. To trigger the modal, I use the data-bs-toggle="modal" ...

Experiencing issue: {"errors":[{"message":"A query string must be provided."}]}

Here is the code snippet from my app.js file: const express = require('express'); const bodyParser = require('body-parser'); const graphqlHttp = require('express-graphql').graphqlHTTP; const { buildSchema } = require('gra ...

How can I extract an array of objects from an array of arrays containing objects?

Here is the array I am currently working with: let findAllSegmentProjectMediaFilesXref = [ { it: 0, bt: 0, items: [ { mute: false, startPosition: 10, endPosition: 20, ...

The Type {children: Element; } is distinct and does not share any properties with type IntrinsicAttributes

I am encountering an issue in my React application where I am unable to nest components within other components. The error is occurring in both the Header component and the Search component. Specifically, I am receiving the following error in the Header co ...

Creating a replica of the "mandatory" text field error message using Material UI

Currently, I am using a simple TextField component from Material UI in my React project (noting the use of "required"). <TextField label="Last name" name="lastName" required value={this.state.lastName} onChange={this.handleChange} / ...

What is the best way to eliminate query parameters in NextJS?

My URL is too long with multiple queries, such as /projects/1/&category=Branding&title=Mobile+App&about=Lorem+ipsum+Lorem+. I just want to simplify it to /projects/1/mobile-app. I've been struggling to fix this for a week. While I found so ...

Maximizing the potential of typescript generics in Reactjs functional components

I have a component within my react project that looks like this: import "./styles.css"; type InputType = "input" | "textarea"; interface ContainerProps { name: string; placeholder: string; as: InputType; } const Conta ...

When the Fetch response displays a 401 Unauthenticated error, it typically indicates an issue with authentication in a React frontend

For a university assignment, I developed a Spring Boot backend that requires authorization/authentication. The API functions correctly when tested standalone through Postman requests with the correct authentication/authorization. However, when attempting t ...

Setting the type of a prop dynamically based on another prop value

Consider the following scenario with an interface: interface Example { Component: React.ReactElement; componentProperties: typeof Example.Component; } Is there a way to determine the type of properties expected by a passed-in custom component? For ...

Verify if function is returning sessionStorage using jest

Recently, I've been working on creating a jest test for the function below that sets a sessionStorage entry: /** * @desc create authenticated user session * @param {String} [email=''] * @param {Date} [expires=Date.now()] * @param {St ...

Tips for aggregating the values of object arrays in React props

I need help sorting three top-rated posts. Currently, the function displays three post titles along with their ratings, but they are not sorted by best rating. Can anyone assist me with this issue? {posts.slice(0, 3).sort((a, b) => ...

How to change the display property in CSS from block to none using React

Just started diving into React, and I am struggling with making the content disappear when clicking a button. Tried using state and even checked out https://jsfiddle.net/uwadhwnr/, but nothing seems to work. Feeling quite frustrated at the moment. <div ...

Tips for utilizing localStorage within server components in the NextJS 13 app directory

Currently, I am utilizing an API call within the Metadata to retrieve information based on the inputted URL. The next/headers are used to access the URL. export async function generateMetadata( { params, searchParams }: MetadataProps, parent: Resolving ...

Encountering a React JS error with Admin on Rest while making an API request

When making an API call (GET) with React.js using Admin on Rest, I encountered an issue. The server returns data when calling localhost:5001/cities, but there seems to be an error on the client side as indicated by the browser log: failure.js:18 Error: Th ...

Tips for creating a responsive React Monaco Editor:

I'm currently working with react-monaco-editor and having trouble making it responsive. The component has a fixed height and width, so when I resize the screen, the width stays the same. <MonacoEditor width="1200" height=& ...

The MUI Tabs are not reflecting the TabIndicator Color as set in the Global Theme

My issue is with the tabs in my NavBar that keep reverting back to the primary main color (white) of my global theme. I tried setting an override on the component, which works initially, but resets to the primary main color after refreshing the page. Wheth ...

Using Sinon to create a mock of a function

I'm having trouble figuring out a simple task like the one below: render() { return ( <div className="messageDetail"> <div className="messageForm" > Name: <input id="senderMsgName" value={this.props.nameVa ...

What is the best way to retrieve a cookie sent from the server on a subdomain's domain within the client request headers using getServerSideProps

Currently, I have an express application using express-session running on my server hosted at api.example.com, along with a NextJS application hosted at example.com. While everything functions smoothly locally with the server setting a cookie that can be r ...

Preserve the timestamp of when the radio query was chosen

I'm interested in finding a way to save the user's selected answer for a radio button question and track the time they saved it. Is there a way to achieve this using HTML alone? Or would I need to utilize another coding language or package? Just ...

What is the definition of the term "WebapiError"?

I'm currently developing a Spotify Web App that focuses on retrieving the top albums of KD Rusha using the Client ID and Artist ID to exclusively fetch his releases on Spotify. To accomplish this, I am utilizing an npm package called spotify-web-api-n ...