[英]React state updates without a setState function - Unexpected
我有幾個聊天組件, chat
(父)、 CreateMessage
(子)和DisplayMessages
(子)。 所有三個組件將在下面顯示。
用戶使用CreateMessage
組件創建消息。 它將它保存在 useState 掛鈎indivMessages
中,存儲在父Chat
組件中。
indivMessages
被發送到DisplayMessages
組件。 它根據用戶 ID 顯示消息以及同一作者的消息分組。
問題是, indivMessages
state 正在使用formattedMessages
的值進行設置,該值僅在DisplayMessages
的useEffect
掛鈎內設置。
為什么indivMessages
設置為formattedMessages
的值?
為了這個例子,我注釋掉了所有套接字的東西,以便在無菌環境中工作 - 兩種方式都會發生相同的結果。
聊天.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>
);
}
CreateMessage.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
數組更改時,都會執行此 function。
它通過檢查最新消息的作者 ( 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]});
,但我還沒有真正深入閱讀該代碼)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.