【问题标题】:How do I prevent infinite useEffect loop in this case?在这种情况下,如何防止无限的 useEffect 循环?
【发布时间】:2022-01-11 20:27:04
【问题描述】:

我对如何删除消息数组的无限循环感到困惑。我的代码看起来像这样。如果有人可以帮助我,那就太好了。

从 "react" 导入 React, { useEffect, useState };

import classes from "./Chat.module.css";
import StarOutlineOutlinedIcon from "@mui/icons-material/StarOutlineOutlined";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import { selectRoomId } from "../../src/features/appSlice";
import ChatInput from "./ChatInput";
import { useSelector } from "react-redux";
import {
  collection,
  query,
  orderBy,
  getFirestore,
  doc,
  getDoc,
  getDocs,
} from "firebase/firestore";
import Message from "./Message";

function Chat() {
  const roomId = useSelector(selectRoomId);
  const [channelNameData, setChannelNameData] = useState("");
  const [messages, setChannelMessages] = useState([]);
  useEffect(() => {
    console.log('running........');
    async function getRoomDetails() {
      const db = getFirestore();
      const docRef = roomId && doc(db, "rooms", roomId);
      const docSnap = roomId && (await getDoc(docRef));
      if (docSnap?.exists()) {
        setChannelNameData(docSnap.data().name);
      }
    }
    async function getRoomMessages() {
      const db = getFirestore();

      const roomMessages =
        (await roomId) &&
        query(
          collection(db, "rooms", roomId, "messages"),
          orderBy("timestamp", "asc")
        );
      if (roomMessages) {
        const querySnapShot = await getDocs(roomMessages);
        const finalMessages = [];
        querySnapShot.forEach((doc) => {
          finalMessages.push({
            id: doc.id,
            value: doc.data(),
          });
        });
        setChannelMessages(finalMessages);
      }
    }

    getRoomDetails().then(() => {
      getRoomMessages();
    });
  }, [roomId]);

  useEffect(()=>{
    console.log('running2....');
    console.log(messages);
    async function getRoomMessages() {
      const db = getFirestore();

      const roomMessages =
        (await roomId) &&
        query(
          collection(db, "rooms", roomId, "messages"),
          orderBy("timestamp", "asc")
        );
      if (roomMessages) {
        const querySnapShot = await getDocs(roomMessages);
        const finalMessages = [];
        querySnapShot.forEach((doc) => {
          finalMessages.push({
            id: doc.id,
            value: doc.data(),
          });
        });
        setChannelMessages(finalMessages);
      }
    }
    getRoomMessages();
  },[messages, roomId])

  return (
    <div className={classes["chat-container"]}>
      <div className={classes.header}>
        <div className={classes["header-left"]}>
          <h4>
            <strong>#{channelNameData}</strong>
          </h4>
          <StarOutlineOutlinedIcon className={classes.star} />
        </div>
        <div className={classes["header-right"]}>
          <p>
            <InfoOutlinedIcon className={classes.info} /> Details
          </p>
        </div>
      </div>

      <div className={classes["chat-messages"]}>
        {messages?.map((doc) => {
          const { message, timestamp, user, userImage } = doc.value;
          return (
            <Message
              key={doc.id}
              message={message}
              timestamp={timestamp}
              user={user}
              userimage={userImage}
            >
              {message}
            </Message>
          );
        })}
      </div>

      <ChatInput channelName={channelNameData} channelId={roomId} />
    </div>
  );
}

export default Chat;

每次添加新消息时,我都需要获取消息。我已经坚持了很长一段时间。有人可以帮忙吗?

【问题讨论】:

  • 你能解释一下为什么当react中的消息对象发生变化时,你需要再次从数据库中查询消息吗?
  • 每当添加新消息时,我都需要更改 UI。我怎样才能做到这一点?也没有直接的事件监听器。有什么解决办法吗? @HOERNSCHEN
  • 并将消息添加到 Firebase 数据库?
  • 是的。没错。

标签: reactjs firebase react-hooks


【解决方案1】:

您的无限循环是因为您的 useEffect 依赖项中有“消息”。

当您在此 useEffect 中调用 setChannelMessages 时,“消息”会发生变化,然后再次调用 useEffect。

我真的不明白为什么您在此效果的依赖项中有消息,因为它仅用于控制台日志,但如果它是有意的,那么您应该在之前测试消息和 finalMessages 是否不同(使用类似 https://lodash.com/docs/4.17.15#isEqual 的东西)调用 setChannelMessages

【讨论】:

  • 每当添加新消息时,我都需要更改 UI。我怎样才能做到这一点?也没有直接的事件监听器。有什么解决办法吗?
【解决方案2】:

您可以使用每秒(1000 毫秒)获取消息的 Javascript 间隔来代替第二个 useEffect。

最重要的是,您可以将 React 中的消息与 Firebase 中的消息进行比较,并且只有在发生更改时才重置消息。

import classes from "./Chat.module.css";
import StarOutlineOutlinedIcon from "@mui/icons-material/StarOutlineOutlined";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import { selectRoomId } from "../../src/features/appSlice";
import ChatInput from "./ChatInput";
import { useSelector } from "react-redux";
import {
  collection,
  query,
  orderBy,
  getFirestore,
  doc,
  getDoc,
  getDocs,
} from "firebase/firestore";
import Message from "./Message";

function Chat() {
  const roomId = useSelector(selectRoomId);
  const [channelNameData, setChannelNameData] = useState("");
  const [messages, setChannelMessages] = useState([]);

  useEffect(() => {

    async function getRoomDetails() {
      const db = getFirestore();
      const docRef = roomId && doc(db, "rooms", roomId);
      const docSnap = roomId && (await getDoc(docRef));
      if (docSnap?.exists()) {
        setChannelNameData(docSnap.data().name);
      }
    }
    async function getRoomMessages() {
      const db = getFirestore();

      const roomMessages =
        (await roomId) &&
        query(
          collection(db, "rooms", roomId, "messages"),
          orderBy("timestamp", "asc")
        );
      if (roomMessages) {
        const querySnapShot = await getDocs(roomMessages);
        const finalMessages = [];
        querySnapShot.forEach((doc) => {
          finalMessages.push({
            id: doc.id,
            value: doc.data(),
          });
        });
        // Check if messages changed
        if (JSON.stringify(finalMessages) !== JSON.stringify(messages)) {
          setChannelMessages(finalMessages);
        }
      }
    }
    // Create an interval that pulls the new messages every 1000 ms
    let interval;
    getRoomDetails().then(() => {
      interval = setInterval(() => {
        getRoomMessages();
      }, 1000);
    });
    return () => clearInterval(interval)
  }, [roomId]);

  return (
    <div className={classes["chat-container"]}>
      <div className={classes.header}>
        <div className={classes["header-left"]}>
          <h4>
            <strong>#{channelNameData}</strong>
          </h4>
          <StarOutlineOutlinedIcon className={classes.star} />
        </div>
        <div className={classes["header-right"]}>
          <p>
            <InfoOutlinedIcon className={classes.info} /> Details
          </p>
        </div>
      </div>

      <div className={classes["chat-messages"]}>
        {messages?.map((doc) => {
          const { message, timestamp, user, userImage } = doc.value;
          return (
            <Message
              key={doc.id}
              message={message}
              timestamp={timestamp}
              user={user}
              userimage={userImage}
            >
              {message}
            </Message>
          );
        })}
      </div>

      <ChatInput channelName={channelNameData} channelId={roomId} />
    </div>
  );
}

export default Chat;

【讨论】:

  • 非常感谢。这是我很久以来所需要的..
  • 但是 cleartimer 功能运行不正常。因为它没有清除状态
  • “清除状态”是什么意思?当组件不再可见时它还在运行吗?
  • 使用该方法,计时器并不清楚,因此,即使我在应用程序的任何其他页面中,也会调用 useEffect。 @HOERNSCHEN
【解决方案3】:

根据您编写的 cmets,我了解到您想要的是在 firebase 集合(经典聊天应用程序)发生更改后更新 UI 中的消息。

在我看来有两种选择:

  1. 每隔 X 秒轮询一次 firebase 集合以获取最新消息。当然这会产生很多不必要的流量,是不利的。这是polling hook 的示例实现。

  2. 您创建一个将新消息推送到您的 React 应用程序的 websocket 连接。这将是要走的路,但当然也是最难的路。由于我不熟悉firebase,我不确定是否有这样的websocket功能。但如果这是您想要实现的目标,我建议您将 firebase 标签添加到您的问题中。

【讨论】:

    猜你喜欢
    • 2020-03-28
    • 1970-01-01
    • 1970-01-01
    • 2023-04-03
    • 2012-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多