没有 setState 函数的 React 状态更新 - 意外
React state updates without a setState function - Unexpected
我有几个聊天组件,chat
(父)、CreateMessage
(子)和 DisplayMessages
(子)。所有三个组件都将在下面显示。
用户使用 CreateMessage
组件创建消息。它保存在 useState 挂钩中,indivMessages
,存储在父 Chat
组件中。
indivMessages
被发送到 DisplayMessages
组件。它显示消息,并根据用户 ID 将同一作者的消息分组。
问题是,indivMessages
状态正在使用 formattedMessages
中的值进行设置,该值仅在 DisplayMessages
中的 useEffect
挂钩内设置。
为什么 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>
);
}
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
数组更改时都会执行此函数。
它通过检查最新消息的作者 (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]});
,但我还没有真正深入阅读该代码)。
我有几个聊天组件,chat
(父)、CreateMessage
(子)和 DisplayMessages
(子)。所有三个组件都将在下面显示。
用户使用 CreateMessage
组件创建消息。它保存在 useState 挂钩中,indivMessages
,存储在父 Chat
组件中。
indivMessages
被发送到 DisplayMessages
组件。它显示消息,并根据用户 ID 将同一作者的消息分组。
问题是,indivMessages
状态正在使用 formattedMessages
中的值进行设置,该值仅在 DisplayMessages
中的 useEffect
挂钩内设置。
为什么 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>
);
}
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
数组更改时都会执行此函数。
它通过检查最新消息的作者 (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]});
,但我还没有真正深入阅读该代码)。