【问题标题】:SetState inside useEffect causing infite loop after fetching data from firebase从firebase获取数据后,useEffect中的SetState导致无限循环
【发布时间】:2021-08-29 11:26:27
【问题描述】:

我试图以setEventsList 状态拥有eventList,但我总是得到一个无限循环。我已成功从 firebase 获取数据作为对象,我还想将对象更改为数组,以便 map 函数不会抛出错误。

import React, { useEffect, useState } from 'react';
import { realDB } from '../../firebase/firebase'
import Card from '../events/events'

function CardList(props) {
  const [eventsList, setEventsList] = useState([]);
  useEffect(() => {
    const eventsRef = realDB.ref('/Events').limitToFirst(5);
    eventsRef.on('value', (snapshot) => {
      var events = snapshot.val();
      let eventList = []
      for (let id in events) {
        eventList.push({
          EventName: events[id].EventName,
          EventEntryFee: events[id].EventEntryFee,
          Eventsport: events[id].Eventsport,
          eventCurrentParticipants: events[id].eventCurrentParticipants,
          EventMaximumParticipants: events[id].EventMaximumParticipants,
          EventTotalPrizes: events[id].EventTotalPrizes,
          EventDifficulty: events[id].EventDifficulty
        });
      }
      setEventsList(eventList);
      console.log(eventsList);
    });
  }, [])

  return (
    <div>
      {eventsList
        ? eventsList.map((event, index) => (
          <Card key={index} />
        )) : (
          <><p> No data</p></>
        )}
    </div>
  );
}

export default CardList;
// contents of '../../firebase/firebase'

import firebase from "firebase";
import 'firebase/auth'

// Initialize Firebase
const app = firebase.initializeApp({
  apiKey: "AIzaSyBxxxxx1wQA",
  authDomain: "fantasxxxxxxxaxxpp.x",
  databaseURL: "https:xxxxxe.x.com",
  projectId: "fxxxxxe",
  storageBucket: "fantxxxm",
  messagingSenderId: "xxxx",
  appId: "1:xxx",
  measurementId: "Gxx-xxxxx"
});

// firebase.analytics();
const db = app.firestore()
const realDB = app.database()
const auth = app.auth()
export { db, auth, realDB }

【问题讨论】:

  • eventsRef 是否需要取消订阅才能移除侦听器并结束状态更新?
  • 我还是新手。我只想将 eventsList 的状态设置为 eventList 数组并将其映射到卡片组件。如何在当前代码不出现无限循环的情况下做到这一点?
  • 我认为不应该
  • 不太确定,useEffect 钩子只运行一次,所以要么发生了许多onValue 事件,要么这个CardList 组件被反复重新安装,所以每次都运行效果。您知道该事件多久发出一次吗?您可以使用useEffect(() =&gt; console.log('MOUNTED'), []); 轻松测试重新安装理论,如果您看到一堆,那么您知道它正在重新安装。
  • @DrewReese 只有一次

标签: reactjs firebase firebase-realtime-database react-hooks


【解决方案1】:

在您当前的代码中,存在许多可能导致问题的错误。

首先,无论何时卸载组件,您都不会取消订阅快照侦听器。如果父组件移除/读取你的CardList 组件,这将导致更新状态超出范围等问题。

您不应该使用for (let id in snapshot.val()),因为您的数据库查询中的排序将被丢弃。相反,您应该使用 Firebase SDK 提供的 DataSnapshot#forEach 方法。

我还建议注意 Firebase RTDB,注意 ReferenceQuery 之间的区别,因为它们提供的 DataSnapshot 对象必须以不同的方式使用。

在 React 中使用数组时,应注意为使用 map 插入的每个子元素提供 key 属性。在您当前的代码中,您为此使用了数组的索引,但您应该使用条目的数据库键来代替,以便 React 可以有效地处理组件移动或被删除。同样,您的“无数据”条目应该有一个类似的键。

您谈到了如果您的map 函数设置不正确会导致错误,而不是仅使用eventsList,您还应该处理数据仍在加载的情况。您可以使用Event[] | null 类型或使用单独的const [loading, setLoading] = useState(true) 来处理此问题。

最后,您的代码中没有任何错误处理逻辑。这可以使用控制台日志来处理,但您应该设置一个errorMsg 状态变量,以便在出现问题时向用户显示一条消息。

因此需要进行以下更改:

  • 正确取消订阅值监听器
  • 使用DataSnapshot#forEach
  • eventsRef 重命名为eventsQuery
  • snapshot 重命名为querySnapshot
  • 应用处理加载状态的方式。
  • 应用错误处理。
import React, { useEffect, useState } from 'react';
import { realDB } from '../../firebase/firebase'
import Card from '../events/events'

function CardList(props) {
  const [eventsList, setEventsList] = useState(null); // null -> loading
  const [errorMsg, setErrorMsg] = useState(null); // null -> no error

  useEffect(() => {
    const eventsQuery = realDB.ref('/Events').limitToFirst(5);

    const valueListener = eventsQuery.on(
      'value',
      (querySnapshot) => {
        const eventList = [];
        querySnapshot.forEach(eventSnapshot => {
          const eventData = eventSnapshot.val();
          eventList.push({
            _key: eventSnapshot.key;
            EventName: eventData.EventName,
            EventEntryFee: eventData.EventEntryFee,
            Eventsport: eventData.Eventsport,
            eventCurrentParticipants: eventData.eventCurrentParticipants,
            EventMaximumParticipants: eventData.EventMaximumParticipants,
            EventTotalPrizes: eventData.EventTotalPrizes,
            EventDifficulty: eventData.EventDifficulty
          });
        }
        console.log("refreshing eventsList", eventsList);
        setEventsList(eventList);
        setErrorMsg(null);
      },
      (error) => {
        setEventsList([]);
        setErrorMsg("Something went wrong.");
      }
    );

    return () => eventsQuery.off('value', valueListener);
  }, []);

  if (eventsList === null) {   // is loading?
    return (<div><key="loading"><p>loading...</p></></div>);
  }

  if (errorMsg !== null) {     // has error?
    return (<div><key="error"><p>{errorMsg}</p></></div>);
  }

  return (
    <div>
      { eventsList.length > 0
        ? eventsList.map((event) => (
          <Card key={event._key} />
        )) : (
          <key="nodata"><p>No data</p></>
        )
      }
    </div>
  );
}

export default CardList;

如果错误仍然存​​在,您需要查看父组件,看看它是否正在快速重新渲染。

旁注: eventCurrentParticipants 应重命名为 EventCurrentParticipantsEventsportEventSport 以与您的其余数据保持一致。虽然,在每个属性的开头使用Event 也是多余的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-27
    • 2020-11-16
    • 1970-01-01
    • 2021-11-01
    • 1970-01-01
    相关资源
    最近更新 更多