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
相关产品推荐
相关产品推荐

