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

React应用与Firebase状态不同步:初始渲染无法加载聊天消息

React聊天组件初始渲染无法获取Firebase对话数据的问题解决

问题原因分析

  1. 依赖数组不全导致首次获取逻辑未触发
    你的useEffect仅依赖numberOfMessagesSent,但初始挂载时,currentUserClickedOn[0]可能因usersListCTX或params未就绪而处于undefined状态,此时asyncWarperFetchDiscussionInDb里的判断会跳过数据获取。而且当usersListCTX更新后,useEffect也不会重新触发,导致无法获取到对话数据。

  2. 状态更新方式存在隐患
    提交消息时setNumberOfMessagesSent(numberOfMessagesSent + 1)使用的是快照值,在并发更新场景下可能出现状态不同步的问题。

解决方案

1. 完善useEffect依赖并确保数据获取时机正确

将currentUserClickedOn[0]和currentUserContext加入useEffect的依赖数组,确保当这些关键数据就绪时,自动触发对话数据的获取。同时用useMemo缓存currentUserClickedOn,避免不必要的重计算。

2. 使用函数式更新状态

修改setNumberOfMessagesSent为函数式更新,确保每次都基于最新状态值进行更新。

修改后的完整代码

import React, { useContext, useEffect, useState, useMemo } from "react";
import "./card-medium-message.style.scss";
import likeEmpty from "./like-empty.png";
import likeFull from "./like-full.png";
import cancel from "./cancel.png";
import send from "./send.png";
import back from "./back.png";
import { useNavigate, useParams } from "react-router-dom";
import { UsersListContext } from "../../context/usersList-context/users-list-context";
import { UserContext } from "../../context/user-context/user-context";
import {
  createDiscussionInDb,
  goFetchDiscussionInDb,
  goFetchDisscussion,
} from "../../utils/firebase";

const CardMediumMessage = () => {
  const params = useParams();
  const { usersListCTX } = useContext(UsersListContext);
  const { currentUserContext } = useContext(UserContext);
  
  // 用useMemo缓存过滤结果,避免每次渲染都重新计算
  const currentUserClickedOn = useMemo(() => {
    return usersListCTX.filter(user => user.displayName === params.name);
  }, [usersListCTX, params.name]);

  const [messages, setMessages] = useState([]);
  const [inputText, setInputText] = useState("");
  const [numberOfMessagesSent, setNumberOfMessagesSent] = useState(0);

  const asyncWarperFetchDiscussionInDb = async () => {
    // 同时判断currentUserContext和currentUserClickedOn[0]是否就绪
    if (currentUserContext?.displayName && currentUserClickedOn[0]) {
      const discussion = await goFetchDiscussionInDb(
        currentUserContext.displayName,
        currentUserClickedOn[0].displayName
      );
      // 确保discussion.messages存在再更新状态
      setMessages(discussion?.messages || []);
    }
  };

  const asyncWarperCreateDiscussionInDb = async () => {
    if (currentUserContext?.displayName && currentUserClickedOn[0]) {
      await createDiscussionInDb(
        currentUserContext.displayName,
        currentUserClickedOn[0].displayName,
        inputText
      );
      resetField();
    }
  };

  useEffect(() => {
    // 首次挂载就执行获取逻辑,无需等待消息发送
    asyncWarperFetchDiscussionInDb();
  }, [currentUserClickedOn[0], currentUserContext]); // 添加关键依赖

  useEffect(() => {
    // 仅当消息发送次数变化且不为0时,执行创建/更新对话
    if (numberOfMessagesSent !== 0) {
      asyncWarperCreateDiscussionInDb();
      // 执行完创建后重新获取最新消息
      asyncWarperFetchDiscussionInDb();
    }
  }, [numberOfMessagesSent, currentUserClickedOn[0], currentUserContext]);

  const messageSubmit = async (e) => {
    e.preventDefault();
    if (!inputText.trim()) return;
    // 函数式更新状态,确保基于最新值
    setNumberOfMessagesSent(prev => prev + 1);
  };

  const textChanged = (e) => {
    setInputText(e.target.value);
  };

  const resetField = () => {
    setInputText("");
  };

  const navigate = useNavigate();

  return (
    <div className="card-medium-warp">
      <div className="card-medium-message">
        <div className="section1" onClick={() => navigate(-1)}>
          <div className="profile-image-outer-circle">
            {currentUserClickedOn[0] && (
              <img
                src={`https://api.dicebear.com/5.x/micah/svg?seed=${currentUserClickedOn[0].displayName}`}
                alt="avatar"
                className="profile-image"
              />
            )}
          </div>
          {currentUserClickedOn[0] && (
            <h2 className="name">{currentUserClickedOn[0].displayName}</h2>
          )}
          <div
            className="back"
            style={{ backgroundImage: `url(${back})` }}
          ></div>
        </div>
        <div className="section2">
          {messages.map((messageObject, index) => (
            <p
              key={index}
              className={
                messageObject.by === currentUserContext?.displayName
                  ? "sender-message"
                  : "receiver-message"
              }
            >
              {messageObject.message}
            </p>
          ))}
        </div>
        <form className="section3" onSubmit={messageSubmit}>
          <input
            type="text"
            className="input"
            placeholder="your message"
            onChange={textChanged}
            value={inputText}
            autoFocus
          />
          <div
            className="send-message"
            style={{ backgroundImage: `url(${send})` }}
          ></div>
        </form>
      </div>
    </div>
  );
};

export default CardMediumMessage;

关键改动说明

  • useMemo缓存过滤结果:避免usersListCTX或params变化时重复执行过滤操作,同时确保依赖数组能正确感知到currentUserClickedOn的变化。
  • 拆分useEffect:将数据获取和消息提交的逻辑拆分到两个useEffect中,职责更清晰。首次挂载时单独触发数据获取,无需依赖消息发送次数。
  • 完善就绪判断:在异步函数中同时判断currentUserContext和currentUserClickedOn[0]是否就绪,避免因上下文未加载导致的错误。
  • 函数式状态更新:确保numberOfMessagesSent的更新基于最新状态,避免并发更新时的异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 19:10:29