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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 21:57:24