React useEffect首次加载Firebase返回空数据问题解决方案
问题描述
在useEffect钩子中向Firebase发起API调用时,首次加载页面返回的数据为空,仅当修改代码触发热更新保存后数据才会正常填充。首次加载阶段因为空数据调用map方法会抛出错误,数据填充完成后再次提交即可正常运行,期望实现首次页面加载时就能正常拉取所需数据。
对应原实现代码:
const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [users, setUsers] = useState({}); const [status, setStatus] = useState({}); const userDetailsRef = collection(db, "userDetails"); const userStatusRef = collection(db, "userStatus"); const handleSubmit = (event) => { event.preventDefault(); users.map((user) => { if (email === user.email && password === user.password) { getEmail(); } }); }; const getEmail = async () => { const data = await getDocs(userStatusRef); setStatus(data.docs.map((doc) => ({ ...doc.data(), id: doc.id }))); console.log(status); status.map((em) => { if (email === em.email) { updateStatus(em.id); } }); }; const updateStatus = async (id) => { const userDoc = doc(db, "userStatus", id); const status = { status: "online" }; await updateDoc(userDoc, status); console.log("updated status"); }; useEffect(() => { getUsers(); }, []); const getUsers = async () => { const data = await getDocs(userDetailsRef); setUsers(data.docs.map((doc) => ({ ...doc.data(), id: doc.id }))); console.log(users); };
报错截图如下:
问题根源
报错和首次加载异常由3个核心问题导致:
- 状态初始值类型错误:
users和status初始值设为空对象{},但后续接口返回的是数组类型并赋值给这两个状态。对象本身没有.map方法,首次渲染时接口还未返回,直接调用users.map就会抛出类型错误 - 对React状态更新机制理解错误:React的
setState是异步批更新逻辑,调用状态更新方法后立刻读取状态,拿到的永远是更新前的旧值,在setUsers/setStatus之后立刻打印、遍历对应状态,自然拿不到刚请求回来的新数据 - 缺少加载态兜底:接口请求是异步过程,首次渲染到请求返回之间存在空窗期,没有做拦截处理就直接操作还未赋值的状态,必然会触发异常
修复方案
调整状态初始类型,增加加载兜底,将依赖请求结果的逻辑直接基于本地返回值处理,不要依赖刚更新的状态,修复后完整代码如下:
import { useState, useEffect } from "react"; import { collection, getDocs, doc, updateDoc } from "firebase/firestore"; // 替换为你自己项目的Firebase配置引入路径 import { db } from "./firebase/config"; export default function LoginPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); // 初始值改为空数组,和接口返回的数组类型对齐 const [users, setUsers] = useState([]); const [status, setStatus] = useState([]); // 新增加载状态,标记用户数据是否拉取完成 const [loading, setLoading] = useState(true); const userDetailsRef = collection(db, "userDetails"); const userStatusRef = collection(db, "userStatus"); const handleSubmit = async (event) => { event.preventDefault(); // 数据未加载完成时拦截提交 if (loading || !users.length) return; // 查找匹配用户用find替代map,语义更准确、性能更好 const matchedUser = users.find(user => email === user.email && password === user.password); if (matchedUser) { await getEmail(); } }; const getEmail = async () => { const data = await getDocs(userStatusRef); // 请求结果先存入本地变量,直接基于该变量做后续逻辑 const statusList = data.docs.map((doc) => ({ ...doc.data(), id: doc.id })); setStatus(statusList); const matchedStatus = statusList.find(em => email === em.email); if (matchedStatus) { await updateStatus(matchedStatus.id); } }; const updateStatus = async (id) => { const userDoc = doc(db, "userStatus", id); const newStatus = { status: "online" }; await updateDoc(userDoc, newStatus); console.log("用户状态已更新为在线"); }; useEffect(() => { const getUsers = async () => { try { const data = await getDocs(userDetailsRef); const userList = data.docs.map((doc) => ({ ...doc.data(), id: doc.id })); setUsers(userList); } catch (err) { console.error("拉取用户列表失败:", err); } finally { // 无论请求成功失败,都关闭加载态 setLoading(false); } }; getUsers(); // 补全依赖项,避免闭包引用异常 }, [userDetailsRef]); // 加载中展示提示,避免渲染表单时操作空数据 if (loading) { return <div>用户数据加载中,请稍候...</div>; } return ( <form onSubmit={handleSubmit}> <div> <label>邮箱:</label> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required /> </div> <div> <label>密码:</label> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required /> </div> <button type="submit">登录</button> </form> ); }
关键修改点说明
- 将
users、status的初始值从空对象改为空数组,和接口返回的数组类型保持一致,即使数据未加载完成,调用数组方法也不会抛出类型错误 - 新增
loading加载状态,请求完成前拦截提交操作、展示加载提示,从根源避免空窗期操作空数据的问题 - 异步请求拿到结果后,先将结果存入本地变量执行后续逻辑,不再依赖
setState更新后的状态值,绕开React状态异步更新导致的拿不到最新值的问题 - 将遍历匹配用的
map方法替换为更合适的find方法,避免无意义的数组遍历,提升性能 - 给
useEffect补全正确的依赖项,避免闭包导致的引用异常 - 增加请求错误捕获逻辑,避免接口报错时页面直接崩溃
内容的提问来源于stack exchange,提问作者Jayashree K
相关产品推荐
相关产品推荐

