You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

react-window中FixedSizeGrid嵌套InfiniteLoader不触发loadMoreItems求助

解决react-window FixedSizeGrid搭配InfiniteLoader不触发加载更多的问题

你遇到的核心问题来自两个关键逻辑错误:

  • isItemLoaded函数逻辑错误:原代码直接返回全局加载状态,而InfiniteLoader需要通过该函数判断指定索引的条目是否已加载,而非当前是否在加载中。
  • Grid的rowCount设置错误:原代码用已加载产品数计算行数,导致InfiniteLoader误以为没有更多条目需要加载,自然不会触发loadMoreItems。

代码修改方案

1. 修正isItemLoaded函数

将函数改为根据索引判断对应产品是否存在于已加载列表中:

isItemLoaded={(index) => !!products[index]}

2. 调整Grid的rowCount计算

用总产品数计算总行数,让Grid明确知道还有更多内容待加载:

rowCount={Math.ceil(TOTAL_PRODUCTS / columnCount)}

3. 处理未加载条目的渲染

因为rowCount基于总产品数,滚动时会提前渲染未加载的条目位置,需要添加占位内容:

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

    // 产品未加载时显示占位
    if (!product) {
        return (
            <div style={style} key={index}>
                <Paper sx={{ m: 3, display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }} elevation={3}>
                    <Typography>加载中...</Typography>
                </Paper>
            </div>
        );
    }

    // 原有产品渲染逻辑
    return (
        <div style={style} key={product.id}>
            {/* 原代码内容 */}
        </div>
    );
};

4. 优化状态更新的闭包问题

在fetchProducts中使用函数式更新,避免因闭包导致的状态错误:

const fetchProducts = async (pageNumber) => {
    setIsLoading(true);
    try {
        const response = await axios.get(
            `http://localhost:5000/products?_page=${pageNumber}&_limit=${PAGE_SIZE}`
        );
        const newProducts = response.data;
        // 用函数式更新获取最新状态
        setProducts(prevProducts => [...prevProducts, ...newProducts]);
        setCurrentPage(prevPage => prevPage + 1);
    } catch (error) {
        console.error('Error fetching products:', error);
    } finally {
        setIsLoading(false);
    }
};

完整修改后的代码

import React, { useEffect, useState } from "react";
import Image from 'next/image'
import { 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 columnCount = 3; // Number of columns (items per row)

const ProductList = () => {
    const [products, setProducts] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    const [currentPage, setCurrentPage] = useState(1);

    const TitleTypography = styled(Typography)({
        fontFamily: poppinsBold.style.fontFamily,
        color: 'black',
    });

    const MediumTypography = styled(Typography)({
        fontFamily: poppinsBold.style.fontFamily,
        color: 'black',
    });

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

    const loadMoreItems = () => {
        if (!isLoading && products.length < TOTAL_PRODUCTS) {
            fetchProducts(currentPage);
        }
    };

    useEffect(() => {
        fetchProducts(currentPage);
    }, []);

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

        if (!product) {
            return (
                <div style={style} key={index}>
                    <Paper sx={{ m: 3, display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }} elevation={3}>
                        <Typography>加载中...</Typography>
                    </Paper>
                </div>
            );
        }

        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="产品图片"
                            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={(index) => !!products[index]}
                itemCount={TOTAL_PRODUCTS}
                loadMoreItems={loadMoreItems}
            >
                {({ onItemsRendered, ref }) => (
                    <Grid
                        height={window.innerHeight}
                        width={window.innerWidth}
                        columnCount={columnCount}
                        columnWidth={window.innerWidth / columnCount}
                        rowCount={Math.ceil(TOTAL_PRODUCTS / columnCount)}
                        rowHeight={200}
                        onItemsRendered={onItemsRendered}
                        ref={ref}
                    >
                        {renderItem}
                    </Grid>
                )}
            </InfiniteLoader>
        </Box>
    )
}

export default ProductList;

替代方案

如果react-window的Grid组合InfiniteLoader仍有兼容问题,可以考虑:

  • 使用react-virtualized的Grid组件搭配其内置的无限滚动逻辑,生态更成熟。
  • 手动实现滚动监听:给Grid容器添加scroll事件,判断滚动位置接近底部时触发加载更多。

内容的提问来源于stack exchange,提问作者Vishwa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 10:43:11