简体   繁体   English

反应 state 更新没有 setState function - 意外

[英]React state updates without a setState function - Unexpected

I have a few chat components, chat (parent), CreateMessage (child), and DisplayMessages (child).我有几个聊天组件, chat (父)、 CreateMessage (子)和DisplayMessages (子)。 All three components will be shown below.所有三个组件将在下面显示。

The user creates a message with the CreateMessage component.用户使用CreateMessage组件创建消息。 It saves it in the useState hook, indivMessages , stored in the parent Chat component.它将它保存在 useState 挂钩indivMessages中,存储在父Chat组件中。

The indivMessages are sent to the DisplayMessages component. indivMessages被发送到DisplayMessages组件。 It displays the messages as well as groups messages by the same author together based off the user id.它根据用户 ID 显示消息以及同一作者的消息分组。

The problem is, the indivMessages state is getting set with the value from formattedMessages , which is only set inside the useEffect hook in DisplayMessages .问题是, indivMessages state 正在使用formattedMessages的值进行设置,该值仅在DisplayMessagesuseEffect挂钩内设置。

Why is indivMessages getting set with the value for formattedMessages ??为什么indivMessages设置为formattedMessages的值?

For the sake of this example, I commented out all of the socket stuff to just work in a sterile environment - the same results will happen both ways.为了这个例子,我注释掉了所有套接字的东西,以便在无菌环境中工作 - 两种方式都会发生相同的结果。

Chat.js聊天.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 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 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>
  );
}

I am thinking that something is going on within the useEffect hook in the DisplayMessages component.我在想DisplayMessages组件中的useEffect挂钩中发生了一些事情。 This function executes every time the indivMessages array changes.每次indivMessages数组更改时,都会执行此 function。

It creates grouped messages by checking if the newest message's author ( ownerId ), is the same as the previous message's author.它通过检查最新消息的作者 ( ownerId ) 是否与前一条消息的作者相同来创建分组消息。 If they are the same, it extracts all the messages from the message itself, and adds them to the previous message's message key in order to create grouped messages.如果它们相同,它将从消息本身中提取所有消息,并将它们添加到前一条消息的消息键中,以创建分组消息。

This result is what is being set in the indivMessages array, which is unexpected since I do not set indivMessages with grouped messages!这个结果是在indivMessages数组中设置的,这是出乎意料的,因为我没有设置带有分组消息的indivMessages

Thanks for your help and thanks for some pointers in the right direction!感谢您的帮助,并感谢您在正确方向上的一些指示!

In the useEffect code, you're modifying the objects in sortedArray , which are also held in indivMessages .useEffect代码中,您正在修改sortedArray中的对象,这些对象也保存在indivMessages中。 For instance:例如:

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

Your code setting up sortedArray just copies the array , not the objects within it.您设置sortedArray的代码只是复制数组,而不是其中的对象。 If you want to modify those objects, you need to make copies first.如果要修改这些对象,则需要先制作副本。 For instance, the example above becomes:例如,上面的示例变为:

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

...or similar. ...或类似的。

You have the same sort of problem with:你有同样的问题:

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

...but probably want to solve it elsewhere (perhaps by changing grouped.push(sortedArray[i]); to grouped.push({...sortedArray[i]}); , but I haven't done a really deep read of that code). ...但可能想在其他地方解决它(也许通过将grouped.push(sortedArray[i]);更改为grouped.push({...sortedArray[i]}); ,但我还没有真正深入阅读该代码)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM