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

React无限滚动功能初始数据重复显示问题求助

React无限滚动初始数据重复显示问题排查与解决

问题现象

  • 页面加载完成后,初始数据集(1-9)被正确获取并展示;
  • 初始加载阶段,该数据集(1-9)会被显示两次,直到滚动触发后续页面加载;
  • 滚动过重复数据段后,后续页面(第10条及以后)的数据才能正常展示。

代码示例

import React, { useEffect, useState } from 'react';
import MovieComponent from './MovieComponent';
import Loading from './Loading';

const Home = () => {
    const [card, setCard] = useState([]);
    const [page, setPage] = useState(1);
    const [loading, setLoading] = useState(true);

    const getCardData = async () => {
        const res = await fetch(
            `https://jsonplaceholder.typicode.com/posts?_limit=9&_page=${page}`
        );
        const data = await res.json();
        console.log("data got is ", data);
        setCard((prev) => [...prev, ...data]);
        setLoading(false);
    };

    useEffect(() => {
        getCardData();
    }, [page]);

    const handleInfiniteScroll = () => {
        try {
            if (
                document.documentElement.scrollTop + window.innerHeight + 1 >=
                document.documentElement.scrollHeight
            ) {
                setLoading(true);
                setPage((prev) => prev + 1);
            }
        } catch (error) {
            console.log(error);
        }
    };

    useEffect(() => {
        window.addEventListener('scroll', handleInfiniteScroll);
        return () => window.removeEventListener('scroll', handleInfiniteScroll);
    }, []);

    return (
        <>
            <MovieComponent movieInfo={card} />
            {loading && <Loading />}
        </>
    );
};

export default Home;

问题核心原因

  1. React严格模式的重复执行:开发环境下,React严格模式会触发useEffect执行两次,导致初始时两次调用getCardData(page=1),重复添加相同数据;
  2. 初始页面高度不足触发滚动事件:如果初始加载的9条数据高度小于窗口高度,滚动事件的触发条件会直接满足,提前触发page递增,但异步请求的时序问题可能导致重复请求同一页数据。

解决方案

方案1:阻止严格模式下的初始重复请求

通过useRef标记初始请求是否完成,避免严格模式下的重复调用:

import React, { useEffect, useState, useRef } from 'react';
// ...其他导入不变

const Home = () => {
    const [card, setCard] = useState([]);
    const [page, setPage] = useState(1);
    const [loading, setLoading] = useState(true);
    const isInitialFetchDone = useRef(false); // 标记初始请求完成状态

    const getCardData = async () => {
        // 初始请求已完成则直接返回
        if (page === 1 && isInitialFetchDone.current) return;
        
        const res = await fetch(
            `https://jsonplaceholder.typicode.com/posts?_limit=9&_page=${page}`
        );
        const data = await res.json();
        
        // 数据为空时停止监听,避免无效请求
        if (data.length === 0) {
            setLoading(false);
            window.removeEventListener('scroll', handleInfiniteScroll);
            return;
        }
        
        setCard((prev) => [...prev, ...data]);
        setLoading(false);
        
        // 标记初始请求完成
        if (page === 1) isInitialFetchDone.current = true;
    };

    // ...其他代码不变
};

方案2:添加请求锁防止并发请求

新增状态标记是否正在请求,避免同一时间发起多个重复请求:

const Home = () => {
    const [card, setCard] = useState([]);
    const [page, setPage] = useState(1);
    const [loading, setLoading] = useState(true);
    const [isFetching, setIsFetching] = useState(false); // 请求锁

    const getCardData = async () => {
        // 正在请求则直接返回
        if (isFetching) return;
        setIsFetching(true);

        try {
            const res = await fetch(
                `https://jsonplaceholder.typicode.com/posts?_limit=9&_page=${page}`
            );
            const data = await res.json();
            
            if (data.length === 0) {
                window.removeEventListener('scroll', handleInfiniteScroll);
                return;
            }
            
            setCard((prev) => [...prev, ...data]);
        } catch (error) {
            console.log(error);
        } finally {
            setLoading(false);
            setIsFetching(false); // 释放请求锁
        }
    };

    // ...其他代码不变
};

方案3:延迟添加滚动监听

等初始数据渲染完成后再添加滚动监听,避免初始时因页面高度不足触发不必要的请求:

useEffect(() => {
    // 初始数据加载完成后再绑定滚动监听
    if (card.length > 0) {
        window.addEventListener('scroll', handleInfiniteScroll);
        return () => window.removeEventListener('scroll', handleInfiniteScroll);
    }
}, [card]); // 依赖card状态,数据更新后再执行

总结

优先组合方案1和方案2,既能解决React严格模式下的重复请求问题,又能防止并发请求导致的重复数据,同时优化滚动触发逻辑避免无效请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 20:12:17