React状态更新没有setState函数-意外



我有几个聊天组件,chat(父(、CreateMessage(子(和DisplayMessages(子(。以下将显示所有三个组件。

用户使用CreateMessage组件创建一条消息。它将其保存在useState钩子indivMessages中,该钩子存储在父Chat组件中。

indivMessages被发送到DisplayMessages组件。它显示消息,并根据用户id将同一作者的消息分组在一起。

问题是,indivMessages状态被设置为来自formattedMessages的值,该值仅在DisplayMessagesuseEffect钩子内设置。

为什么indivMessages被设置为formattedMessages的值??

为了这个例子,我注释掉了所有套接字的东西,只在无菌环境中工作——两种方式都会产生相同的结果。

Chat.js

import React, { useState, useEffect, useContext } from "react";
import { useSelector } from "react-redux";
import { SocketContext } from "src/SocketContext";
import CreateMessage from "./details/CreateMessage";
import DisplayMessages from "./details/DisplayMessages";
export default function Chat(props) {
const state = useSelector((state) => state);
const [indivMessages, setIndivMessages] = useState([]);
const socket = useContext(SocketContext);
// useEffect(() => {
//   if (state.chatShow) {
//     socket.emit("SUBSCRIBE_CHAT", state.chat.chatRoom);
//     return () => {
//       socket.emit("UNSUBSCRIBE", state.chat.chatRoom);
//     };
//   }
// });
// useEffect(() => {
//   socket.on("new_message", (data) => {
//     setIndivMessages([...indivMessages, data]);
//   });
//   return () => {
//     socket.off("new_message");
//   };
// }, [socket, indivMessages]);
return (
<div className="d-flex flex-column h-100 justify-content-end">
<DisplayMessages state={state} indivMessages={indivMessages} />
<CreateMessage
state={state}
indivMessages={indivMessages}
setIndivMessages={setIndivMessages}
/>
</div>
);
}

创建消息.js

import React, { useState, useContext } from "react";
import { CInputGroup, CInput, CInputGroupAppend, CButton } from "@coreui/react";
import CIcon from "@coreui/icons-react";
import { SocketContext } from "src/SocketContext";
export default function CreateMessage(props) {
const { indivMessages, setIndivMessages, state } = props;
const [newMessage, setNewMessage] = useState("");
const socket = useContext(SocketContext);
const sendMessage = () => {
let messageTemplate = {
messages: [{ msg: newMessage }],
username: state.user.username,
_id: indivMessages.length + 1,
ownerId: state.user._id,
picture: state.user.picture,
chatRoom: state.chat.chatRoom,
date: Date.now(),
};
// socket.emit("create_message", messageTemplate);
setIndivMessages((msgs) => [...msgs, messageTemplate]);
document.getElementById("msgInput").value = "";
};
return (
<CInputGroup style={{ position: "relative", bottom: 0 }}>
<CInput
type="text"
style={{ fontSize: "18px" }}
id="msgInput"
className="rounded-0"
placeholder="Type a message here..."
autoComplete="off"
onChange={(e) => setNewMessage(e.target.value)}
onKeyUp={(e) => e.code === "Enter" && sendMessage()}
/>
<CInputGroupAppend>
<CButton color="success" className="rounded-0" onClick={sendMessage}>
<CIcon name="cil-send" />
</CButton>
</CInputGroupAppend>
</CInputGroup>
);
}

DisplayMessages.js

import React, { useEffect, useState } from "react";
import { CContainer, CCard, CImg, CCol, CLabel } from "@coreui/react";
export default function DisplayMessages(props) {
const { indivMessages, state } = props;
const [formattedMessages, setFormattedMessages] = useState([]);
useEffect(() => {
//Create Grouped Messesges
let messagesArray = [...indivMessages];
let sortedArray = messagesArray.sort((a, b) => {
return new Date(a.date) - new Date(b.date);
});
let grouped = [];
for (let i = 0; i < sortedArray.length; i++) {
let index = grouped.length - 1;
if (sortedArray[i].ownerId === grouped[index]?.ownerId) {
let lastMessage = grouped.pop();
sortedArray[i].messages[0]._id = sortedArray[i]._id;
lastMessage.messages = [
...lastMessage.messages,
sortedArray[i].messages[0],
];
grouped.push(lastMessage);
} else {
console.log(i, grouped.length);
grouped.push(sortedArray[i]);
}
}
setFormattedMessages(grouped);
}, [indivMessages]);
useEffect(() => {
let msgContainer = document.getElementById("msgContainer");
msgContainer.scrollTop = msgContainer.scrollHeight;
}, [formattedMessages]);
return (
<CContainer
className="mt-2 no-scroll-bar"
style={{ overflow: "auto", maxHeight: "85vh" }}
id="msgContainer"
>
{formattedMessages.map((msg) => {
return (
<CCard
key={msg._id}
className="d-flex flex-row p-2"
color="secondary"
accentColor={state.user._id === msg.ownerId && "primary"}
>
<CImg
src={msg.picture}
alt={msg.owner}
className="w-25 align-self-start rounded"
/>
<CCol>
<CLabel>
<strong>{msg.username}</strong>
</CLabel>
{msg.messages.map((message) => {
return (
<p key={message._id ? message._id : msg._id}>{message.msg}</p>
);
})}
</CCol>
</CCard>
);
})}
</CContainer>
);
}

我认为DisplayMessages组件中的useEffect挂钩中发生了一些事情。每当indivMessages数组发生变化时,都会执行此函数。

它通过检查最新消息的作者(ownerId(是否与前一条消息的作者相同来创建分组消息。如果它们相同,它会从消息本身提取所有消息,并将它们添加到前一条消息的消息键中,以创建分组消息。

这个结果就是在indivMessages数组中设置的结果,这是出乎意料的,因为我没有用分组消息设置indivMessages

感谢您的帮助,也感谢您为我们指明了正确的方向!

useEffect代码中,您正在修改sortedArray中的对象,这些对象也保存在indivMessages中。例如:

sortedArray[i].messages[0]._id = sortedArray[i]._id;

设置sortedArray的代码只是复制数组,而不是其中的对象。如果要修改这些对象,需要先进行复制。例如,上面的例子变成:

sortedArray[i] = {
...sortedArray[i],
messages: [{...sortedArray[i].messages[0], _id = sortedArray[i]._id}, ...sortedArray[i].messages.slice(1)],
};

或类似的。

你也有同样的问题:

lastMessage.messages = [
...lastMessage.messages,
sortedArray[i].messages[0],
];

但可能想在其他地方解决它(也许通过将grouped.push(sortedArray[i]);更改为grouped.push({...sortedArray[i]});,但我还没有真正深入阅读该代码(。

最新更新