I am working with a navigation bar that should be able to switch between multiple chat rooms using react and react-firebase-hooks. (https://github.com/CSFrequency/react-firebase-hooks)
However, the chat room will infinitely re-render itself when I choose a room in nav-bar.
I initially thought this is a router issue, but having each rooms sharing the same url, the issue persists.
Right now, when I choose a room using the nav bar, it will send that room number back to App.js using a callback function. App.js will pass that room number to ChatRoom.js, which will get the data from firestore, and re-render itself.
I struggled for several days trying to find anything that could cause the infinite loop with minimal success. Any help would be appreciated!
ChatRoom.js
import React, { useMemo, useRef, useState } from 'react';
import { withRouter } from 'react-router';
import { useCollectionData, useDocument, useDocumentData } from 'react-firebase-hooks/firestore';
import firebase, { firestore, auth } from '../Firebase.js';
import ChatMessage from './ChatMessage';
const ChatRoom2 = (props) => {
console.log("chat room rendered");
function saveQuery(){
const channelid= props.channelid;
const messagesRef = firestore.collection('messages').doc(channelid).collection('chats');
const query = messagesRef.orderBy('createdAt').limitToLast(25);
return [messagesRef,query];
}
var returnedVal = useMemo(()=>saveQuery , [props.channelid]);
const messagesRef = returnedVal[0];
const query = returnedVal[1];
const [messages] = useCollectionData(query, { idField: 'id' });
const [formValue, setFormValue] = useState('');
const sendMessage = async (e) => {
e.preventDefault();
console.log(messagesRef);
console.log(query);
console.log(messages);
const { uid, photoURL } = auth.currentUser;
await messagesRef.add({
text: formValue,
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
uid,
photoURL
})
setFormValue('');
}
return (<>
<main>
{messages && messages.map(msg => <ChatMessage key={msg.id} message={msg} />)}
</main>
<form onSubmit={sendMessage}>
<input value={formValue} onChange={(e) => setFormValue(e.target.value)} placeholder="say something nice" />
<button type="submit" disabled={!formValue}>🕊️</button>
</form>
</>)
}
export default ChatRoom2;
ChatList.js (nav bar)
const ChatList = (props) => {
console.log("list rendered");
const query = firestore.collection('users').doc(auth.currentUser.uid).collection('strangers').orderBy('channelID').limitToLast(10);
//console.log(query);
const [channelidArr] = useCollectionData(query, { idField: 'id' });
return (
<div>
{channelidArr && channelidArr.map(channelid =>
<div>
<button onClick={() => props.parentCallback(channelid.channelID)}>{channelid.channelID}</button>
<br />
</div>)}
</div>
);
};
export default ChatList;
App.js
import React, { useRef, useState } from 'react';
import {
BrowserRouter,
Switch,
Route,
Link
} from "react-router-dom";
//import './App.css';
import firebase, { firestore, auth } from './Firebase.js';
import { useAuthState } from 'react-firebase-hooks/auth';
import { useCollectionData } from 'react-firebase-hooks/firestore';
import ChatList from './components/ChatList.js';
import FindNew from './components/FindNew.js';
import Footer from './components/Footer.js';
import Profile from './components/Profile.js';
import ChatRoom2 from './components/ChatRoom2.js';
import SignOut from './components/SignOut.js';
import SignIn from './components/SignIn.js';
import SignUp from './components/SignUp.js';
import ChatRoom from './components/ChatRoom.js';
function App() {
console.log('App rendered');
const [user] = useAuthState(auth);
const [roomNum, setRoomNum] = useState([]);
const callbackFunction = (childData) => {
setRoomNum(childData);
};
return (
<div className="App">
<header>
<h1>⚛️🔥💬</h1>
<SignOut auth={auth} />
</header>
<BrowserRouter >
<Footer />
<Switch>
<Route path="/profile">
<Profile />
</Route>
<Route path="/new">
<FindNew />
</Route>
<Route path="/signup">
{() => {
if (!user) {
return <SignUp />;
} else {
return null;
}
}}
</Route>
<Route path="/direct">
{user ?
<div>
<ChatList parentCallback={callbackFunction} />
<ChatRoom2 channelid={roomNum} />
</div> : <SignIn />}
</Route>
</Switch>
</BrowserRouter>
</div>
);
};
export default App;
useCollectionDataOncehook in the chatroom component? (2) If you comment out theconst [messages] = useCollectionData(query, { idField: 'id' });in chatroom does it still render-loop? Can you include a more completeapp.jscomponent so we can see how the callback androomNumvalue is maintained.querybeing redeclared each render cycle and thatqueryends up being auseCollectionDatahook dependency (I've dug through the source code and I think this is the case). Try using auseMemohook to memoize thequeryvalue with a dependency on thechannelidprop soqueryis a stable reference value for theuseCollectionDatahook.