React加载更多初始page=0重复调用问题求助
问题:React加载更多首次page=0请求两次导致数据重复
实现React滚动加载更多功能时遇到以下问题:
- 首次加载时
pageNum=0被调用两次,导致前3条查询结果重复显示 - 滚动加载
pageNum=1、2时功能完全正常 - 后端采用Python+MongoDB开发
用户提供的代码如下:
import './App.css'; import { debounce } from 'lodash'; const App = () => { const [videos, setVideos] = useState([]); const [page, setPage] = useState(0); const [loading, setLoading] = useState(false); const [hasMore, setHasMore] = useState(true); const limit = 3; const [initialLoad, setInitialLoad] = useState(true); // New state to track initial load useEffect(() => { if (initialLoad) { fetchVideos(page); // Initial load with page 0 setInitialLoad(false); // Set initialLoad to false after initial fetch } }, [page, initialLoad]); // Include page and initialLoad in dependency array const fetchVideos = async (pageNum) => { setLoading(true); try { const response = await fetch(`http://127.0.0.1:5000/videos?page=${pageNum}&limit=${limit}`); const data = await response.json(); console.log('Fetched videos:', data); if (data.length > 0) { setVideos(prevVideos => [...prevVideos, ...data]); setPage(pageNum + 1); } else { setHasMore(false); } } catch (error) { console.error('Error fetching videos:', error); } finally { setLoading(false); } }; const handleScroll = useCallback(() => { if (loading || !hasMore) return; const lastVideo = document.querySelector('.video-list > .video-card:last-child'); if (!lastVideo) return; const lastVideoOffset = lastVideo.offsetTop + lastVideo.clientHeight; const pageOffset = window.pageYOffset + window.innerHeight; const bottomOffset = 20; if (pageOffset > lastVideoOffset - bottomOffset) { fetchVideos(page); } }, [loading, hasMore, page]); useEffect(() => { const handleScrollDebounced = debounce(() => { handleScroll(); }, 300); window.addEventListener('scroll', handleScrollDebounced); return () => window.removeEventListener('scroll', handleScrollDebounced); }, [handleScroll]); return ( <div className="App"> <h1>Music Reels</h1> <div className="video-list"> {videos.map(video => ( <div className="video-card" key={video._id}> <video controls autoPlay muted> <source src={video.video_link} type="video/mp4" /> </video> <div className="video-info"> <h2>{video.title}</h2> <p>{video.description}</p> <p>{new Date(video.uploaded_date).toLocaleDateString()}</p> </div> </div> ))} {loading && <p>Loading...</p>} {!loading && !hasMore && <p>No more videos</p>} </div> </div> ); }; export default App;
解决方案
问题原因
- useEffect依赖数组触发重复执行:原初始加载的
useEffect依赖了page和initialLoad,当setInitialLoad(false)修改状态后,会再次触发该useEffect,此时page仍为0,导致第二次调用fetchVideos(0)。 - React 18 Strict Mode双重执行:在Strict Mode下,React会刻意执行两次挂载相关的生命周期逻辑,用于检测潜在问题,这会加剧重复请求的现象。
修复方案
使用useRef标记初始加载状态(不会触发组件重渲染),并将初始加载逻辑设置为仅在组件挂载时执行一次:
完整修改后代码
import './App.css'; import { debounce } from 'lodash'; import { useState, useEffect, useCallback, useRef } from 'react'; const App = () => { const [videos, setVideos] = useState([]); const [page, setPage] = useState(0); const [loading, setLoading] = useState(false); const [hasMore, setHasMore] = useState(true); const limit = 3; const hasLoadedInitial = useRef(false); // 标记是否完成初始加载 // 仅在组件挂载时执行一次初始加载 useEffect(() => { if (hasLoadedInitial.current) return; hasLoadedInitial.current = true; fetchVideos(0); }, []); const fetchVideos = async (pageNum) => { setLoading(true); try { const response = await fetch(`http://127.0.0.1:5000/videos?page=${pageNum}&limit=${limit}`); const data = await response.json(); console.log('Fetched videos:', data); if (data.length > 0) { setVideos(prevVideos => [...prevVideos, ...data]); setPage(pageNum + 1); } else { setHasMore(false); } } catch (error) { console.error('Error fetching videos:', error); } finally { setLoading(false); } }; const handleScroll = useCallback(() => { if (loading || !hasMore) return; const lastVideo = document.querySelector('.video-list > .video-card:last-child'); if (!lastVideo) return; const lastVideoOffset = lastVideo.offsetTop + lastVideo.clientHeight; const pageOffset = window.pageYOffset + window.innerHeight; const bottomOffset = 20; if (pageOffset > lastVideoOffset - bottomOffset) { fetchVideos(page); } }, [loading, hasMore, page]); useEffect(() => { const handleScrollDebounced = debounce(() => { handleScroll(); }, 300); window.addEventListener('scroll', handleScrollDebounced); return () => window.removeEventListener('scroll', handleScrollDebounced); }, [handleScroll]); return ( <div className="App"> <h1>Music Reels</h1> <div className="video-list"> {videos.map(video => ( <div className="video-card" key={video._id}> <video controls autoPlay muted> <source src={video.video_link} type="video/mp4" /> </video> <div className="video-info"> <h2>{video.title}</h2> <p>{video.description}</p> <p>{new Date(video.uploaded_date).toLocaleDateString()}</p> </div> </div> ))} {loading && <p>Loading...</p>} {!loading && !hasMore && <p>No more videos</p>} </div> </div> ); }; export default App;
修复说明
useRef的current属性不会触发组件重渲染,标记初始加载状态后不会重复触发useEffect。- 空数组依赖的
useEffect仅在组件挂载时执行一次,彻底避免重复请求,同时兼容React 18 Strict Mode的双重执行机制。
内容的提问来源于stack exchange,提问作者Kumar Divyanshu
相关产品推荐
相关产品推荐

