reactjs 为什么我的状态在收到事件时改变了值?

wecizke3  于 2023-06-22  发布在  React
关注(0)|答案(1)|浏览(143)

我有这个DashboardNavbar组件,其中有两个主要状态,notifications存储所有用户通知,current存储当前选择的通知。
form这个组件,当openNotificationBoxtrue时,它显示NotificationBox,当current不是null时,它显示ConversationBox(因为每个通知都有一个messages数组,我想显示它,所以我把current传递给ConversationBox,并在那里显示对话)
dashboard.js:

import NotificationBox from "./notification-box/notification-box";
import ConversationBox from "./notification-box/conversation-box";

export const DashboardNavbar = (props) => {
  const socket = useContext(WebsocketContext);
  const [notifications, setNotifications] = useState([]);
  const [openNotificationBox, setOpenNotificationBox] = useState(false);
  const [current, setCurrent] = useState(null); // current notification

  console.log('dashboard is rerendering...')
  console.log(current);

  const handleSetCurrentNotification = (notification) => {
    setCurrent(notification);
  };

  const handleCloseNotificationBox = () => {
    setOpenNotificationBox(false);
  };
  const desactiveNotification = (id) => {
    // CREATE 'arr' then
    setNotifications(arr);
  };

  const isNotificationForCurrentUser = (payload) => {
    // return TRUE or FALSE
  };

  const isNotificationIsForCurrentNotification = (payload) => {
    // return TRUE or FALSE
  };

  const sortNotificationsByLastMsg = (array) => {
    // return a sorted array 
  };

  async function getUpdatedNotification(id) { // I use this function each time the current notification is updated
    // API CALL to get the updated notification then
    setCurrent(res.data);
  }

  const getNotificationsForCurrentUserAndOrderThem = () => {
    //API CALL and then
    setNotifications(sortNotificationsByLastMsg(res.data));

  };
  useEffect(() => {
    getNotificationsForCurrentUserAndOrderThem();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    socket.on("newNotification", (payload) => {
      console.log("newNotification event received !");
      if (isNotificationForCurrentUser(payload)) getNotificationsForCurrentUserAndOrderThem(); // This may update "notifications"
    });
    socket.on("updateConversation", (payload) => {
      console.log("updateConversation event received !");
      console.log(current); // output current
      if (isNotificationForCurrentUser(payload)) getNotificationsForCurrentUserAndOrderThem(); // This may update "notifications"
      if (current) { // if current is not null 
        if (isNotificationIsForCurrentNotification(payload)) getUpdatedNotification(current._id) // this may update "current"
      }
    });
    return () => {
      console.log("Unregistering Events...");
      socket.off("connect");
      socket.off("newNotification");
      socket.off("updateConversation");
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      {/* ... */}
      {
        openNotificationBox &&
        <NotificationBox
        notifications={notifications}
        desactivenotification={desactiveNotification}
        handleclosenotificationbox={handleCloseNotificationBox}
        getupdatednotification={getUpdatedNotification} /*to run getUpdatedNotification and update "current" from the db */ 
      />
      } 
      {current && (
        <ConversationBox
          current={current}
          getupdatednotification={getUpdatedNotification}
          handlesetcurrentnotification={handleSetCurrentNotification}
        />
      )}
    </>
  );
};

notifications-box.js:

const NotificationBox = ({
  notifications,
  getupdatednotification,
  desactivenotification,
  handleclosenotificationbox,
}) => {
 

  async function desactiveNotificationAndSelectId(notification, dto) {
    // API CALL to update the notification
     handleclosenotificationbox(); // this will set "openNotification" false
     desactivenotification(notification._id); // this will update "notifications" in dashboard
     getupdatednotification(notification._id); // this will update "current" in dashboard from db 
  }
  return (
    <div className={classes.notifiBox} id="box">
      {notifications.map((notification) => {      
        //...
        return (
          <div
            key={notification._id}
            onClick={() => {
              if (conditionOne) {
                if (conditionTwo) {
                    desactiveNotificationAndSelectId(notification, {
                    status: "NONE",
                  });
                } else {
                  handleclosenotificationbox(); 
                  getupdatednotification(notification._id); // this will update "current" in dashboard from db
                }
              } else {
                handleclosenotificationbox();
                getupdatednotification(notification._id); // this will update "current" in dashboard from db
              }
            }}
          >
            <img src={image} alt="img" />
            <div className={classes.text}>
              <h4>{notification.title}</h4>
              <p>{notification.messages[notification.messages.length - 1].content}</p>
            </div>
          </div>
        );
      })}
    </div>
  );
};

export default NotificationBox;

conversation-box.js:

const ConversationBox = ({ current, handlesetcurrentnotification }) => {
  const socket = useContext(WebsocketContext);
  async function sendMessage(content) {
    // API CALL TO to UPDATE the notification in the db with the new message then
    handlesetcurrentnotification(res.data); // This will update "current" in the dashboard from the response, this endpoint is returning the updated notification, so there is no need to call getUpdatedNotification
    socket.emit("newMessage", payload); // emit an event
  }

  return (
    <div >
      {/* navtop */}
      <div >
        <div
          onClick={() => {
            handlesetcurrentnotification(null); // This will set "current" to null so close the conversation component
          }}
        >
          <img src="left-chevron.svg" />
          <p>Close</p>
        </div>
        {/* ... */}
      </div>
      {/* conversation */}
      <div >
        <div >
          {current && (
            <>
              {current.messages.map((message, index) => {
                //...
                return (
                  <div key={index}>
                    <img src="avatar2.jpg" />
                    <p>{message.content}</p>
                  </div>
                );
              })}
            </>
          )}
        </div>
      </div>
      {/* chatForm */}
      <div className={classes.chatForm} onSubmit={handleSubmit}>
        <div >
          <div ></div>
          <div>
            <textarea
              placeholder="Enter your message here"
            ></textarea>
          </div>
          <button
            onClick={() => {
              sendMessage("test");
            }}
          >
            <img src="send.svg" />
          </button>
        </div>
      </div>
    </div>
  );
};

export default ConversationBox;

这工作正常,问题出在useEffect内部的部分,当 Jmeter 板组件监听事件。
尤其是这里:

socket.on("updateConversation", (payload) => {
      console.log("updateConversation event received !");
      console.log(current); // THIS LINE
      if (isNotificationForCurrentUser(payload)) getNotificationsForCurrentUserAndOrderThem(); // this works fine
      if (current) { // if current is not null 
        if (isNotificationIsForCurrentNotification(payload)) getUpdatedNotification(current._id)
      }
    });

我用两个不同的浏览器尝试了一个场景,比如说两个不同的用户。
经过调试找到导致问题的原因后,似乎“有时”即使current在接收事件时是nullcurrent也会从null传递到其先前的值,而viseversa有时会从对象传递到null
我不断切换openNotificationBox来重新呈现组件,看看这行输出了什么:

console.log('dashboard is rerendering...')
console.log(current); // <--- output null

那么当接收到一个事件时,useEffect里面这行:

socket.on("updateConversation", (payload) => {
   console.log("updateConversation event received !");
   console.log(current); // <-- output a notification object

所以这段代码运行:

if (current) { // if current is not null 
  if (isNotificationIsForCurrentNotification(payload)) getUpdatedNotification(current._id)
}

我不明白这是怎么可能的,为什么current在这里改变了值!.

这可能发生在输出current之前

if (isNotificationForCurrentUser(payload)) getNotificationsForCurrentUserAndOrderThem();

但这根本不影响current
我的意思是有时是当我重新加载应用程序并重新安装 Jmeter 板组件并开始侦听/等待事件时,这不会发生,这很奇怪,它只发生在current设置为某个通知对象然后返回null时。
感谢您的关注。

mspsb9vt

mspsb9vt1#

在大多数情况下,我们在JSX中使用事件处理程序,如onClickonChange...所以每次组件重新渲染时,这些函数都会被重新创建,因此我们不会面临这种问题,这里据我所知,“updateConversation”的事件处理函数根本不会被重新创建,因为它在组件首次安装时被设置为事件,所以当currentnull时,任何状态或 prop 更改都不会被跟踪,这就是原因。
我发现,每次current更新时,我都需要重新创建这些事件处理程序,方法是将其包含在useEffect的依赖数组中,以使后者在current每次获得新值时运行,现在当事件被触发时,我可以从事件处理程序函数访问current获得的最新值,即当前渲染期间的值:

useEffect(() => {
 socket.on("newNotification", (payload) => {
   //...
 });
 socket.on("updateConversation", (payload) => {
   /...
 });
 return () => {
   socket.off("newNotification");
   socket.off("updateConversation");
 };
}, [current]);

我把这个作为一个解决方案发布,找不到更好的解决方案,但仍然不相信组件应该取消订阅/订阅setCurrent触发的每个组件渲染上的事件。

相关问题