React应用与Firebase状态不同步:初始渲染无法加载聊天消息
React聊天组件初始渲染无法获取Firebase对话数据的问题解决
问题原因分析
依赖数组不全导致首次获取逻辑未触发
你的useEffect仅依赖numberOfMessagesSent,但初始挂载时,currentUserClickedOn[0]可能因usersListCTX或params未就绪而处于undefined状态,此时asyncWarperFetchDiscussionInDb里的判断会跳过数据获取。而且当usersListCTX更新后,useEffect也不会重新触发,导致无法获取到对话数据。状态更新方式存在隐患
提交消息时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
相关产品推荐
相关产品推荐

