I am confused as to how to remove the infinite loop for the messages array. My code looks like this. It would be great if someone can help me with this.
import React, { useEffect, useState } from "react";
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;
I need to fetch messages every time a new message is added. I have been stuck on this for quite some time. Can someone help?
CodePudding user response:
Your infinite loop is because you have "messages" in your useEffect dependency.
When you call setChannelMessages in this useEffect, "messages" change and then the useEffect is called again.
I don't really get why you have messages in this effect's dependencies as it is only used for a console log but if it is intended than you should test that messages and finalMessages are different (using something like https://lodash.com/docs/4.17.15#isEqual) before calling setChannelMessages
CodePudding user response:
Instead of a second useEffect you can use a Javascript Interval that gets the messages each second (1000 ms).
On top you can compare the messages in React with the messages in Firebase and only reset the messages if there are changes.
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;
CodePudding user response:
From the comments you wrote I understood what you want is to update the messages in your UI once there is a change in the firebase collection (classic chat app).
There are two options from my view:
Poll the firebase collection every X seconds to get the latest messages. Of course this produces a lot of unnecessary traffic and is not favourable. Here is an example implementation of a polling hook.
You create a websocket connection that pushes new messages into your React App. This would be the way to go, but of course also the hardest way. Since I'm not familiar with firebase, I'm not sure if there is such websocket functionality. But if this is what you want to achieve I would recommend to add the firebase tag to your question.
