【问题标题】:React state updates without a setState function - Unexpected在没有 setState 函数的情况下反应状态更新 - 意外
【发布时间】:2021-03-24 14:37:16
【问题描述】:

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

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

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

问题是,indivMessages 状态正在使用来自 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 数组更改时都会执行此函数。

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

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

感谢您的帮助,感谢您在正确方向上的一些指示!

【问题讨论】:

  • 上面有很多的代码。请将其减少到minimal reproducible example,删除任何不必要的复制问题。为了帮助人们帮助您,请使用 Stack Snippets([&lt;&gt;] 工具栏按钮)使您的 MRE 可运行。 Stack Snippets 支持 React,包括 JSX; here's how to do one.

标签: javascript reactjs use-effect use-state


【解决方案1】:

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]});,但我还没有深入阅读该代码)。

【讨论】:

  • 感谢您在上面的原始评论中向我展示 SO sn-ps - 我将在未来使用它们,因为它似乎是一种更好的方式来以可用的方式展示事物! -- grouped.push({...sortedArray[i]}); 很神奇,完全按照预期工作!非常感谢你的帮助。从昨晚 6 点左右开始,我一直在努力想弄清楚为什么会这样。奇怪的是,它是断断续续的!昨晚11点左右开始按预期工作,今天早上又回到上面的问题。谢谢 T.J.非常感谢您的宝贵时间。
  • @NickMcLean - 我很高兴有帮助!快乐编码! :-)
猜你喜欢
  • 2019-12-15
  • 2019-01-22
  • 1970-01-01
  • 2021-01-28
  • 1970-01-01
相关资源
最近更新 更多