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

Next.js+Firebase:useEffect首次加载数据未定义及无限循环问题

问题描述

我有一个从Firestore获取子集合的React组件,代码如下:

import { useEffect, useState } from "react";
import { db } from '../firebase/firebaseInit'
import {useAuth} from '../components/AuthContextProvider'
import { collection, getDocs, setDoc, doc, addDoc } from "firebase/firestore";

type Inputs = {
  name: string,
  gender: string,
  date: any,
}

function Subscriptions({ Component, pageProps }: any) {
  const { user  } = useAuth()
  const [kids, setKids] = useState<any>([])

  useEffect(() => {
    const getKids = async (user: any) => {
      if (user) {
        const KidsCollectionRef = collection(doc(collection(db, "users"), user.uid), "kids")
        const data = await getDocs(KidsCollectionRef)
        setKids(data.docs.map((doc) => ({
          ...doc.data(), id: doc.id
        })))
        console.log(kids)
      }
    }
    getKids(user)
  }, [])

  return (
    <>
      <h1>Welcome back</h1>
      {/* need to loop throug the different prices */}
      <div>
        {kids.map((kid, index) => (
          <span key={index} style={{display: 'block', width: '100%'}}>
            {kid.name}
          </span>
        ))}
      </div>
    </>
    )
}

export default Subscriptions

但只有把useEffect改成下面这样,才能在控制台打印出kids的数据:

useEffect(() => {
    const getKids = async (user: any) => {
      if (user) {
        const KidsCollectionRef = collection(doc(collection(db, "users"), user.uid), "kids")
        const data = await getDocs(KidsCollectionRef)
        setKids(data.docs.map((doc) => ({
          ...doc.data(), id: doc.id
        })))
        console.log(kids)
      }
    }
    getKids(user)
  }, [kids])

这种方式会导致无限循环,显然不合理。控制台会重复输出多次日志。

请问如何让useEffect在获取到Firestore数据时正确更新状态?


解决方案

1. 先搞懂问题出在哪

你最初的代码里console.log(kids)打不出数据,是因为React的状态更新是异步的:调用setKids后,kids不会立刻变成新值,此时打印的还是初始的空数组。

而把kids加进useEffect依赖数组后,每次kids更新都会重新触发useEffect,再次请求数据并更新状态,直接形成无限循环。

2. 两种正确的实现方式

方式一:直接打印刚获取的原始数据

不用等状态更新,拿到Firestore返回的数据后直接打印,同时把依赖数组改成[user](只有用户信息变化时才重新请求):

useEffect(() => {
  const getKids = async (user: any) => {
    if (user) {
      // 简化子集合引用写法,Firestore支持直接传路径参数
      const KidsCollectionRef = collection(db, "users", user.uid, "kids")
      const data = await getDocs(KidsCollectionRef)
      const newKids = data.docs.map((doc) => ({
        ...doc.data(), id: doc.id
      }))
      setKids(newKids)
      console.log(newKids) // 直接打印刚拿到的数据
    }
  }
  getKids(user)
}, [user])

方式二:单独用useEffect监听状态变化

如果需要在kids状态更新后执行操作(比如打印、后续处理),可以新增一个专门监听kids的useEffect:

// 负责获取数据的useEffect,只在user变化时执行
useEffect(() => {
  const getKids = async (user: any) => {
    if (user) {
      const KidsCollectionRef = collection(db, "users", user.uid, "kids")
      const data = await getDocs(KidsCollectionRef)
      setKids(data.docs.map((doc) => ({
        ...doc.data(), id: doc.id
      })))
    }
  }
  getKids(user)
}, [user])

// 专门监听kids状态的useEffect,状态更新时触发
useEffect(() => {
  console.log(kids)
}, [kids])

这样既不会无限循环,又能在状态更新后执行对应逻辑。

3. 额外优化建议

  • 给user定义明确类型,别用any,提升代码稳定性;
  • 列表渲染时用kid.id作为key,别用index,避免列表更新时的性能问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 07:24:18