在Snapshot侦听器上的Firestore中访问反应状态的问题

时间:2020-08-29 00:56:27

标签: reactjs react-native google-cloud-firestore

如果当前正在运行某个动画,我想等待从后端应用状态更新。根据游戏场景,此动画可以运行多次。我正在使用带有钩子和Firestore的react-native。

我的计划是创建一个数组,用于存储传入快照的对象和使用该数据更新状态的函数。当动画结束时,它将设置动画运行为false并删除数组的第一项。我还要编写一个useEffect,如果数组的长度已更改,它将从数组中删除第一项。

我打算通过检查此动画是否正在运行或在最新快照到来时将来更新的数组中是否有项目来实现此功能。如果该条件成立,则将快照和更新功能添加到我的阵列中,否则将立即应用状态更新。我需要在所有3个Firestore侦听器中访问该状态。

但是,在onSnapshot中,如果我尝试访问我的状态,它将为我提供函数渲染时的初始状态。一个例外是,如果使用函数设置状态(在这种情况下为setPlayerIsBetting),则可以访问该状态,并可以通过作为回调传递给setPlayerIsBetting的函数来访问先前的状态。

我可以想到一些可行的解决方案,但是除了第一个解决方案之外,其他所有人都感到很棘手。

  1. 如果我将快照的useEffect修改为不仅在安装组件时运行,还可以获取将来的状态更新吗?我短暂地尝试了一下,但似乎破坏了快照。有人知道如何实现吗?

  2. 通过在所有3个侦听器中调用setPlayerIsBetting来访问状态,并在不应该更新setPlayerIsBetting的99%时间内将其设置为先前状态。如果实际上没有任何更改,它还会重新渲染吗?这还会引起其他问题吗?

  3. 在整个组件生命周期中,将快照和更新功能添加到队列中,而不是仅在动画运行时。这可能不是最佳性能,对吧?我不必为动画运行后的最初计划进行一些状态更新而担心,因为我仍然需要花时间等待动画。

  4. 我可以在后端的任何地方添加我需要的状态,以便它随快照一起出现。

  5. 删除或添加侦听器的某种方法。这感觉真是个坏主意。

  6. redux或某种状态管理工具可以解决此问题吗?为解决这一问题,要实现它需要做很多工作,但是也许我的应用程序在那个时候仍然很有用?

这是我的相关代码:

const Game = ({ route }) => {
  const [playerIsBetting, setPlayerIsBetting] = useState({
    isBetting: false,
    display: false,
    step: Infinity,
    minimumValue: -1000000,
    maximumValue: -5000,
  });
  const [updatesAfterAnimations, setUpdatesAfterAnimations] = useState([]); 
  // updatesAfterAnimations is currently always empty because I can't access the updated playerIsBetting state easily

  const chipsAnimationRunningOrItemsInQueue = (snapshot, updateFunction) => {
    console.log(
      "in chipsAnimationRunningOrItemsInQueue playerIsBetting is: ",
      playerIsBetting
    ); // always logs the initial state since its called from the snapshots. 
// So it doesn't know when runChipsAnimation is added to the state and becomes true. 
// So playerIsBetting.runChipsAnimation is undefined
    const addToQueue =
      playerIsBetting.runChipsAnimation || updatesAfterAnimations.length;
    if (addToQueue) {
      setUpdatesAfterAnimations((prevState) => {
        const nextState = cloneDeep(prevState);
        nextState.push({ snapshot, updateFunction });
        return nextState;
      });
      console.log("chipsAnimationRunningOrItemsInQueue returns true!");
      return true;
    }
    console.log("chipsAnimationRunningOrItemsInQueue returns false!");
    return false;
  };

  // listener 1
  useEffect(() => {
    const db = firebase.firestore();
    const tableId = route.params.tableId;
    const unsubscribeFromPlayerCards = db
      .collection("tables")
      .doc(tableId)
      .collection("players")
      .doc(player.uniqueId)
      .collection("playerCards")
      .doc(player.uniqueId)
      .onSnapshot(
        function (cardsSnapshot) {
          if (!chipsAnimationRunningOrItemsInQueue(cardsSnapshot, updatePlayerCards)) {
            updatePlayerCards(cardsSnapshot);
          }
        },
        function (err) {
          // console.log('error is: ', err);
        }
      );
    return unsubscribeFromPlayerCards;
  }, []);
};

// listener 2
useEffect(() => {
  const tableId = route.params.tableId;
  const db = firebase.firestore();
  const unsubscribeFromPlayers = db
    .collection("tables")
    .doc(tableId)
    .collection("players")
    .onSnapshot(
      function (playersSnapshot) {
        console.log("in playerSnapshot playerIsBetting is: ", playerIsBetting); // also logs the initial state 
        console.log("in playerSnapshot playerIsBetting.runChipsAnimation is: "playerIsBetting.runChipsAnimation); // logs undefined
        if (!chipsAnimationRunningOrItemsInQueue(playersSnapshot, updatePlayers)) {
          updatePlayers(playersSnapshot);
        }
      },
      (err) => {
        console.log("error is: ", err);
      }
    );
  return unsubscribeFromPlayers;
}, []);

// listener 3
useEffect(() => {
  const db = firebase.firestore();
  const tableId = route.params.tableId;
  // console.log('tableId is: ', tableId);
  const unsubscribeFromTable = db
    .collection("tables")
    .doc(tableId)
    .onSnapshot(
      (tableSnapshot) => {
        if (!chipsAnimationRunningOrItemsInQueue(tableSnapshot, updateTable)) {
          updateTable(tableSnapshot);
        }
      },
      (err) => {
        throw new err();
      }
    );
  return unsubscribeFromTable;
}, []);

2 个答案:

答案 0 :(得分:0)

我会使用解决方案#1 :在UseEffect()挂钩中,您可以放置​​一个布尔值标志,因此每个挂钩仅将快照侦听器设置一次。然后将动画状态属性放入useEffect依赖项数组中,以便在动画状态更改时触发每个useEffect钩子,然后可以从该条件运行所需的任何逻辑。

答案 1 :(得分:0)

我最终没有采用我提出的任何解决方案。

我意识到我可以使用ref访问最新状态。此处说明了操作方法:(https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559)这是该帖子的相关代码示例:(https://codesandbox.io/s/event-handler-use-ref-4hvxt?from-embed

解决方案#1可以解决问题,但是这很困难,因为当动画状态更改时,我必须解决运行的清理功能。 (Why is the cleanup function from `useEffect` called on every render?

我可以通过以下方法来解决此问题:让清理函数不调用该函数以从侦听器中取消订阅,并将取消订阅的函数存储在状态中,并将其全部装入useEffect中,然后用第二个参数挂载该组件,以确认所有3个取消订阅的函数已添加到状态。

但是,如果用户在这些功能处于状态之前脱机,我认为可能会发生内存泄漏。