Supabase中range滚动分页失效:重复加载相同内容排查
无限滚动重复加载内容的问题修复
问题根源
- 排序方向不一致:初始获取帖子时使用倒序(
ascending: false),但加载更多时使用正序(ascending: true),导致两次请求的分页偏移量对应的数据范围逻辑混乱,出现重复内容。 - 滚动监听的依赖缺失:滚动监听的
useEffect未添加依赖项,导致handleScroll始终使用组件初始渲染时的fetchMorePosts闭包,可能引发状态捕获异常。
修复方案
- 统一排序方向:将加载更多时的排序逻辑改为和初始加载一致的倒序,确保分页偏移量对应正确的数据范围。
- 修正滚动监听的依赖:为滚动监听的
useEffect添加正确的依赖项,并用useCallback包裹fetchMorePosts函数,避免不必要的监听重复绑定。 - 验证分页偏移计算:确保
from和to的计算符合排序方向下的分页逻辑。
修改后的完整代码
"use client"; import { useEffect, useState, useCallback } from "react"; import supabase from "@/lib/supabaseClient"; import PostCard from "../home/PostCard"; import { motion } from "framer-motion"; interface PostCardProps { author: string; id: string; image: string; snippet: string; author_verification: boolean; title: string; created_at: string; category_name: string; author_image: string; bookmark_count: number; likes_count: number; comment_count: number; profile_id: string; } const PAGE_COUNT = 5; export default function TicketsPage() { const [posts, setPosts] = useState<PostCardProps[]>([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState<string | null>(null); const [nextPage, setNextPage] = useState(1); const [isFetching, setIsFetching] = useState(false); useEffect(() => { const fetchInitialPosts = async () => { try { const { data, error } = await supabase .from("posts") .select("*") .range(0, PAGE_COUNT - 1) .order("created_at", { ascending: false }); if (!error && data) { setPosts(data); } else { setError("An error occurred while fetching posts."); } } catch (error) { setError("An error occurred while fetching posts."); } }; fetchInitialPosts(); }, []); // 使用useCallback包裹,避免每次渲染重新创建函数 const fetchMorePosts = useCallback(async () => { if (isFetching) { return; } setIsFetching(true); try { const from = nextPage * PAGE_COUNT; const to = from + PAGE_COUNT - 1; const { data, error } = await supabase .from("posts") .select("*") .range(from, to) .order("created_at", { ascending: false }); // 统一排序方向为倒序 if (!error) { if (data && data.length > 0) { setPosts((prevPosts) => [...prevPosts, ...data]); setNextPage((prev) => prev + 1); console.log("fetching from:", from); console.log("to:", to); } else { setError("No more posts to fetch."); } } else { setError("An error occurred while fetching more posts."); } } catch (error) { setError("An error occurred while fetching more posts."); } finally { setIsFetching(false); } }, [isFetching, nextPage]); // 添加依赖项 useEffect(() => { const handleScroll = () => { if ( window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight ) { fetchMorePosts(); } }; window.addEventListener("scroll", handleScroll); return () => { window.removeEventListener("scroll", handleScroll); }; }, [fetchMorePosts]); // 添加fetchMorePosts作为依赖 return ( <> <div className="flex flex-col w-full gap-5 px-6 pt-10"> {posts.map((post, index) => ( <motion.div key={post.id} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, ease: [0.25, 0.25, 0, 1], delay: index / 15, }}> <> <div>{index}</div> <PostCard {...post} /> </> </motion.div> ))} {isLoading && <div>Loading...</div>} {error && <div>Error: {error}</div>} </div> </> ); }
额外说明
- 统一排序方向后,分页的
range偏移量会基于倒序(最新内容在前)的逻辑获取下一页数据,确保每次加载的都是未展示过的新内容。 - 使用
useCallback包裹fetchMorePosts并添加正确的依赖项,保证滚动监听能获取到最新的状态和函数引用,避免闭包问题。
内容的提问来源于stack exchange,提问作者UY_SCUTI
相关产品推荐
相关产品推荐

