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

NextJs中Firebase无限滚动报错:.length未定义求助

Next.js + Firebase 无限滚动报错:.length 未定义的调试方案

核心错误原因

你代码里的const lastDocument = submissions.data().length - 1;是直接触发报错的根源:

  • submissions是你通过setSubmissions(querySnapshot.docs)保存的文档快照数组,根本没有.data()方法,直接访问submissions.length才是正确的。
  • 更关键的是,Firebase的startAfter()需要传入最后一个文档的快照对象,不是数组索引,你传数字索引会导致Firebase无法识别,同时submissions.data()本身不存在,直接抛出.length未定义的错误。

其他潜在问题

  1. 滚动事件里直接创建onSnapshot订阅,会导致每次滚动到底部都新增一个订阅,存在严重内存泄漏风险。无限滚动场景下,用一次性查询getDocs更合适(除非你需要实时更新)。
  2. 第二个useEffect的依赖数组只有router,但内部用到了submissions,会触发闭包问题,获取到的submissions可能是旧值。
  3. 没有做加载状态的防重复触发,正在加载时仍可能重复发起请求。

修复后的完整代码示例

import { useState, useEffect } from 'react';
import { collection, query, where, orderBy, limit, startAfter, getDocs, onSnapshot } from 'firebase/firestore';
import { firestore } from '../your-firebase-config'; // 替换成你的Firebase配置路径
import { useRouter } from 'next/router';

export default function YourComponent() {
  const router = useRouter();
  const [submissions, setSubmissions] = useState([]);
  const [isLoadingData, setIsLoadingData] = useState(false);
  const [lastVisible, setLastVisible] = useState(null); // 保存最后一个文档快照,用于分页

  // 初始加载第一页数据(实时监听版本)
  useEffect(() => {
    if (!firestore || !router.query.csite || !router.query.uid) return;

    const initialQuery = query(
      collection(firestore, "roadmap"),
      where("csite", "==", router.query.csite),
      where("uid", "==", router.query.uid),
      orderBy("createdAt", "desc"),
      limit(10)
    );

    const unsubscribe = onSnapshot(initialQuery, (querySnapshot) => {
      const docs = querySnapshot.docs;
      setSubmissions(docs);
      if (docs.length > 0) {
        setLastVisible(docs[docs.length - 1]); // 保存第一页的最后一个文档
      }
    });

    return () => unsubscribe();
  }, [router, firestore]);

  // 处理滚动加载下一页
  useEffect(() => {
    const handleScroll = async () => {
      // 防重复触发:正在加载/无更多数据时直接返回
      if (isLoadingData || !lastVisible) return;
      // 提前100px触发加载,提升用户体验
      if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 100) {
        setIsLoadingData(true);
        try {
          const nextPageQuery = query(
            collection(firestore, "roadmap"),
            where("csite", "==", router.query.csite),
            where("uid", "==", router.query.uid),
            orderBy("createdAt", "desc"),
            limit(10),
            startAfter(lastVisible) // 传入最后一个文档快照
          );

          const querySnapshot = await getDocs(nextPageQuery);
          const newDocs = querySnapshot.docs;
          if (newDocs.length > 0) {
            setSubmissions(prev => [...prev, ...newDocs]);
            setLastVisible(newDocs[newDocs.length - 1]); // 更新最后一个文档快照
          }
        } catch (error) {
          console.error('加载下一页失败:', error);
        } finally {
          setIsLoadingData(false);
        }
      }
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll); // 清理事件监听,避免内存泄漏
  }, [isLoadingData, lastVisible, router.query.csite, router.query.uid, firestore]);

  // 渲染逻辑
  return (
    <div>
      {submissions.map(doc => (
        <div key={doc.id}>
          {/* 替换成你需要展示的文档字段 */}
          {doc.data().content}
        </div>
      ))}
      {isLoadingData && <div>加载中...</div>}
    </div>
  );
}

关键修复点说明

  • 新增lastVisible状态,专门保存每一页的最后一个文档快照,作为startAfter()的参数。
  • 滚动事件改用addEventListener绑定,并在组件卸载时移除,彻底避免内存泄漏。
  • 下一页查询使用getDocs而非onSnapshot,避免重复订阅(如果需要实时更新,可以保留onSnapshot,但要注意取消之前的订阅)。
  • 添加加载状态防重复逻辑,避免同一时间发起多个请求。
  • 修复闭包问题,将所有用到的变量加入useEffect的依赖数组。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 04:02:08