【问题标题】:Infinite loop when working with react and react-firebase-hooks使用 react 和 react-firebase-hooks 时的无限循环
【发布时间】:2021-04-28 06:14:33
【问题描述】:

我正在使用一个导航栏,它应该能够使用 react 和 react-firebase-hooks 在多个聊天室之间切换。 (https://github.com/CSFrequency/react-firebase-hooks)

但是,当我在导航栏中选择一个房间时,聊天室会无限重新渲染。

我最初认为这是一个路由器问题,但让每个房间共享相同的 url,问题仍然存在。

现在,当我使用导航栏选择房间时,它会使用回调函数将该房间号发送回 App.js。 App.js 会将该房间号传递给 ChatRoom.js,后者将从 firestore 获取数据并重新渲染自身。

我挣扎了好几天,试图找到任何可能导致无限循环的东西,但收效甚微。任何帮助将不胜感激!

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(导航栏)

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;

【问题讨论】:

  • 你能分辨出哪个组件在渲染循环吗?您是否尝试过进一步简化代码以隔离哪个组件/钩子的使用触发了渲染循环?
  • 您好,感谢您的回复。聊天室组件导致无限循环,事件发生的顺序是:应用渲染->聊天列表渲染->聊天室渲染->聊天列表渲染->聊天室无限渲染。在 chunk.js 文件中,它说“ const { channelid } = props.channelid; ”导致错误。我是 react 新手,如果我说的某些事情没有任何意义,我很抱歉。
  • 用这段代码创建一个运行的代码框可能很困难(你可以试试)。很少的想法。 (1) 如果你在聊天室组件中使用了useCollectionDataOnce 钩子怎么办? (2) 如果您在聊天室中注释掉const [messages] = useCollectionData(query, { idField: 'id' });,它是否仍然呈现循环?能否包含一个更完整的app.js 组件,以便我们查看回调和roomNum 值是如何维护的。
  • 您好,感谢您再次回复!我试过你的建议。1)在聊天室中使用 useCollectionDataOnce 钩子也会导致无限循环。 2) 删除 useCollectionData 不会导致无限循环。 3) 我更新了 App.js,它现在包含回调函数和 roomNum 状态。
  • 谢谢。好的,我怀疑这可能是由于 query 在每个渲染周期都被重新声明并且 query 最终成为 useCollectionData 挂钩依赖项(我已经挖掘了源代码并且我认为就是这种情况)。尝试使用 useMemo 挂钩来记忆 query 值并依赖于 channelid 属性,因此 queryuseCollectionData 挂钩的稳定参考值。

标签: javascript reactjs firebase google-cloud-firestore react-hooks


【解决方案1】:

问题摘要

useCollectionData 记忆 query 参数,但由于每个渲染周期都声明了一个新的 query reference,因此重新运行 firebase 挂钩并更新集合并重新渲染组件。

const { channelid } = props;
const messagesRef = firestore
  .collection('messages')
  .doc(channelid)
  .collection('chats');
const query = messagesRef // <-- new query reference
  .orderBy('createdAt')
  .limitToLast(25);

const [messages] = useCollectionData(
  query, // <-- reference update trigger hook
  { idField: 'id' },
);

解决方案

query 仅依赖于 channelid 属性值,因此我们可以记住 query 值并将稳定值引用传递给 useCollectionData 钩子。

const { channelid } = props;

const query = useMemo(() => {
  const messagesRef = firestore
    .collection('messages')
    .doc(channelid)
    .collection('chats');
  const query = messagesRef.orderBy('createdAt').limitToLast(25);
  return query;
}, [channelid]);

const [messages] = useCollectionData(
  query,  // <-- stable reference
  { idField: 'id' },
);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-15
    • 2019-10-17
    • 1970-01-01
    • 2021-05-17
    • 2021-05-23
    • 1970-01-01
    • 2020-11-20
    • 2021-03-05
    相关资源
    最近更新 更多