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

React分页异常:为何首次渲染需点击页码才显示数据?

产品列表首次渲染无数据、Loading状态失效问题解决

问题描述

我正在渲染经过筛选的产品,已实现分页功能,但存在以下异常:

  • 首次渲染时无产品数据,必须点击页码(如第1页)后数据才会显示
  • Loading状态未正常生效
    数据来源于后端MongoDB。

首次渲染效果:
首次渲染

点击第1页后效果:
点击第1页后

原代码:

const ProductList = ({products, category}) => {
  const [filteredProducts, setFilteredProducts]  = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() =>{
    const isAvailable = products.filter((product) => product.quantity !== 0 )
    setFilteredProducts(isAvailable)
    setLoading(false)
  },[setFilteredProducts, category,products])

  const firstIndex = 0
  const [pageSize, setPageSize] = useState(5)
  const [page, setPage] = useState(1)
  const [data,setData] = useState(filteredProducts.slice(firstIndex, pageSize))

  useEffect(() =>{
    setData(filteredProducts.slice(0, pageSize))
    setLoading(false)
  },[pageSize])

  const handleChange = (event, value) => {
    setPage(value);
    setData(filteredProducts.slice(firstIndex + pageSize * (value - 1), pageSize * value));
  };

  return (
    <>
    {loading ?
      <BeatLoader 
      color="#36d7b7" 
      loading={loading}
      size={50}
      aria-label="Loading Spinner"
      data-testid="loader"
      />
      :
      (
        <Box sx={{backgroundColor: '#f5f5f5', display:'flex', marginTop:2}}>
      <Container maxWidth="xl">
        <Typography sx={{textAlign: 'center', paddingY: '20px', fontWeight: 700, color: '#212121', typography: {xs: "h6", md: "h4"}}}>Products</Typography>
          <Box sx={{display: 'flex', alignItems:'center',justifyContent: 'space-evenly', flexWrap: 'wrap', gap: '10px'}}>
            {data.map((product) => (
              <ProductItem  key={product._id} product={product} />
              ))}
          </Box>
          <Pagination 
            sx={{display: 'flex', alignItems:'center',justifyContent:'center',margin: 4}}
            size="large"
            count={Math.ceil(filteredProducts.length/pageSize)}
            page={page}
            onChange={handleChange}
          />
      </Container>
    </Box>
    )
  }
  </>
  )
}

export default ProductList

问题根源

  1. 初始数据同步异常:data状态初始化时依赖空的filteredProducts,导致初始data为空;且filteredProducts更新后,没有对应逻辑同步更新data,仅当pageSize变化时才更新,因此首次渲染后data始终为空,直到点击页码触发handleChange才会填充数据。
  2. Loading状态逻辑混乱:多个useEffect随意设置setLoading(false),未准确判断数据加载状态,导致Loading提前失效。
  3. 冗余状态维护:data状态可通过filteredProducts、page、pageSize实时计算,单独维护易引发状态不同步问题。

解决方案

修改后的代码

const ProductList = ({ products, category }) => {
  const [filteredProducts, setFilteredProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [pageSize, setPageSize] = useState(5);
  const [page, setPage] = useState(1);

  useEffect(() => {
    // 过滤可用产品
    const availableProducts = products.filter(product => product.quantity !== 0);
    setFilteredProducts(availableProducts);
    // 仅当传入的products为空时保持loading状态
    setLoading(products.length === 0);
  }, [category, products]);

  // 实时计算当前页数据
  const getCurrentPageData = () => {
    const startIndex = (page - 1) * pageSize;
    const endIndex = startIndex + pageSize;
    return filteredProducts.slice(startIndex, endIndex);
  };

  const handleChange = (event, value) => {
    setPage(value);
  };

  return (
    <>
      {loading ? (
        <BeatLoader
          color="#36d7b7"
          loading={loading}
          size={50}
          aria-label="Loading Spinner"
          data-testid="loader"
        />
      ) : (
        <Box sx={{ backgroundColor: '#f5f5f5', display: 'flex', marginTop: 2 }}>
          <Container maxWidth="xl">
            <Typography
              sx={{
                textAlign: 'center',
                paddingY: '20px',
                fontWeight: 700,
                color: '#212121',
                typography: { xs: "h6", md: "h4" }
              }}
            >Products</Typography>
            <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-evenly', flexWrap: 'wrap', gap: '10px' }}>
              {getCurrentPageData().map(product => (
                <ProductItem key={product._id} product={product} />
              ))}
            </Box>
            <Pagination
              sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', margin: 4 }}
              size="large"
              count={Math.ceil(filteredProducts.length / pageSize)}
              page={page}
              onChange={handleChange}
            />
          </Container>
        </Box>
      )}
    </>
  );
};

export default ProductList;

关键修改说明

  • 移除data状态,通过getCurrentPageData函数实时计算当前页数据,确保filteredProducts更新后立即同步到页面。
  • 优化Loading逻辑:setLoading(products.length === 0),仅当后端返回的products为空时保持加载状态,数据加载完成后自动关闭。
  • 简化分页触发逻辑:handleChange仅更新page状态,数据计算由函数实时处理,避免手动维护data导致的不同步。
  • 清理无效依赖:移除useEffect中的setFilteredProducts,useState的setter函数是稳定值,无需加入依赖数组。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 07:40:33