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

React Firestore路由跳转获取items状态为空及无限循环问题

问题根因
  • 空依赖的useEffect仅在组件首次挂载时执行,此时useAuthState还在加载用户状态,user值为null,直接读取user.uid会导致查询条件无效,根本拉不到对应用户的数据。等后续用户状态加载完成,useEffect不会重新执行,自然拿不到数据,只有热重载/页面刷新时碰巧用户状态已经初始化完成,才能正常拉到数据。
  • 把items、itemCollectionRef加入依赖会触发无限循环的原因:collection(db, "items")写在组件渲染函数内部,每次组件重渲染都会生成全新的集合引用,加上setItems更新状态会触发重渲染,重渲染又生成新引用、再次触发useEffect执行,形成死循环。
  • 拉取数据后console.log("Fetched Items: ", items)打印为空是闭包问题:setItems是异步更新状态,当前useEffect闭包里捕获的items永远是执行effect时的旧值,不代表set操作没有生效。
  • 原代码在渲染分支直接调用navigate("/")属于渲染阶段执行副作用,会触发React渲染警告,不符合React路由的使用规则。
正确实现代码
import React, { useState, useEffect } from "react";
import { Grid, Box } from "@mantine/core";
import { ItemBadge } from "../../components/NFAItemBadge";
import { useNavigate, Navigate } from "react-router-dom";
import { db, auth } from "../../firebase";
import { query, getDocs, collection, where } from "firebase/firestore";
import { useAuthState } from "react-firebase-hooks/auth";

// 固定集合引用移到组件外,避免每次渲染重建引用
const itemCollectionRef = collection(db, "items");

const ItemTrack = () => {
  const [user, loading, error] = useAuthState(auth);
  const navigate = useNavigate();
  const [items, setItems] = useState([]);

  useEffect(() => {
    // 用户状态加载中直接返回,等状态确定再执行逻辑
    if (loading) return;
    // 未登录则跳转
    if (!user) {
      navigate("/");
      return;
    }

    const fetchItems = async () => {
      try {
        const q = query(itemCollectionRef, where("uid", "==", user.uid));
        const querySnapshot = await getDocs(q);
        const fetchedItems = querySnapshot.docs.map(doc => ({
          ...doc.data(),
          id: doc.id
        }));
        setItems(fetchedItems);
        // 要查看拉取结果直接打印处理后的数据,不要打印闭包捕获的旧items
        console.log("Fetched Items: ", fetchedItems);
      } catch (err) {
        console.error("数据拉取失败:", err);
      }
    };

    fetchItems();
    // 仅在加载状态、用户信息变化时重新执行查询
  }, [loading, user, navigate]);

  if (loading) {
    return <div><p>Initialising User....</p></div>;
  }
  if (error) {
    return <div><p>Error: {error.message}</p></div>;
  }
  // 未登录使用声明式跳转组件,避免渲染阶段执行副作用
  if (!user) {
    return <Navigate to="/" replace />;
  }

  return (
    <Box sx={{ maxWidth: 1000 }} mx="auto">
      <Grid>
        {items.map(item => (
          <Grid.Col span={4} key={item.id}>
            <ItemBadge item={item} />
          </Grid.Col>
        ))}
      </Grid>
    </Box>
  );
};

export default ItemTrack;
关键改动说明
  • 固定不变的Firestore集合引用移到组件外部,从根源避免每次渲染重建引用触发不必要的effect执行,如果需要动态生成集合引用,用useMemo包裹固定引用后再加入依赖数组。
  • useEffect依赖数组仅保留loading、user、navigate,既保证只有用户状态就绪时才发起查询,又不会因为依赖频繁变化的state、不稳定的对象引用导致无限重渲染。
  • 修正闭包导致的日志打印异常问题,不需要等待state异步更新即可查看拉取到的最新数据。
  • 替换渲染阶段直接调用navigate的逻辑,改用React Router提供的<Navigate>组件做声明式跳转,符合React渲染规则。
  • 增加请求错误捕获,避免Firestore请求异常时页面白屏。

额外提示:如果需要实时监听Firestore数据变化,不要用getDocs做一次性拉取,改用onSnapshot订阅集合变更,记得在useEffect的return回调中取消订阅,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 17:27:26