React分页异常:为何首次渲染需点击页码才显示数据?
产品列表首次渲染无数据、Loading状态失效问题解决
问题描述
我正在渲染经过筛选的产品,已实现分页功能,但存在以下异常:
- 首次渲染时无产品数据,必须点击页码(如第1页)后数据才会显示
- Loading状态未正常生效
数据来源于后端MongoDB。
首次渲染效果:
点击第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
问题根源
- 初始数据同步异常:
data状态初始化时依赖空的filteredProducts,导致初始data为空;且filteredProducts更新后,没有对应逻辑同步更新data,仅当pageSize变化时才更新,因此首次渲染后data始终为空,直到点击页码触发handleChange才会填充数据。 - Loading状态逻辑混乱:多个
useEffect随意设置setLoading(false),未准确判断数据加载状态,导致Loading提前失效。 - 冗余状态维护:
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
相关产品推荐
相关产品推荐

