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

Supabase中range滚动分页失效:重复加载相同内容排查

无限滚动重复加载内容的问题修复

问题根源

  1. 排序方向不一致:初始获取帖子时使用倒序(ascending: false),但加载更多时使用正序(ascending: true),导致两次请求的分页偏移量对应的数据范围逻辑混乱,出现重复内容。
  2. 滚动监听的依赖缺失:滚动监听的useEffect未添加依赖项,导致handleScroll始终使用组件初始渲染时的fetchMorePosts闭包,可能引发状态捕获异常。

修复方案

  1. 统一排序方向:将加载更多时的排序逻辑改为和初始加载一致的倒序,确保分页偏移量对应正确的数据范围。
  2. 修正滚动监听的依赖:为滚动监听的useEffect添加正确的依赖项,并用useCallback包裹fetchMorePosts函数,避免不必要的监听重复绑定。
  3. 验证分页偏移计算:确保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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 01:30:01