使用useEffect()挂钩和异步/等待从Firebase / Firestore获取数据

时间:2019-10-03 21:59:47

标签: reactjs firebase promise async-await react-redux

1。我的意图:

使用 useEffect() 钩子从props初始化组件的状态变量,这些props的数据是使用 mapStateToProps()从firebase中提取的>


2。问题:无法初始化组件的状态变量

  • 这个想法是,当组件安装时,useEffect将只运行一次。在此初始化窗口中,我想将数据从Firebase加载到此组件的本地状态变量。

  • running the example in this tutorial时工作正常。在其中,他通过 async / await 函数中的axios()获取数据。有关其代码,请参见下面的第3节。

  • 但是,当我这样做时,尝试从与Firestore同步或初始化的道具中获取数据时,我一直获取null,无法从我的async /返回的result中检索数据等待功能。

  • 我认为这是因为我缺乏对异步/等待的理解。我无法创建有效的 async / await 函数来从道具中检索数据。

问题:

我怀疑这就是问题所在。那么,编写 async / await 函数以正确地从与Firebase同步的道具中检索数据的正确方法是什么?

我的问题代码:

    import React, { useState, useEffect} from "react";
    import { connect } from "react-redux";
    import { firestoreConnect } from "react-redux-firebase";
    import { compose } from "redux";

    const app = props => {
      const { order } = props;  
      const [data, setData] = useState({});

      useEffect(() => {
        const fetchData = async () => {
          const result = await (() => {
            return new Promise(resolve => {
              if (order) { resolve(order); }
            });
          })();
          setData(result);
        };
        fetchData();
      }, []);
      /** if I leave useEffect's second argument empty, which is intended to run only once at the beginning, then i'm getting null  */

    ...

    const mapStateToProps = state => {
      const id = "DKA77MC2w3g0ACEy630g"; // docId for testing purpose.
      const orders = state.firestore.data.orders;
      const order = orders ? orders[id] : null;        
      return {
        order: order
      };
    };

    export default compose(
      connect(mapStateToProps),
      firestoreConnect([{collection: "orders"}])
    )(app);

我尝试过的事情:

  • 如果我在第二个参数中添加 order ,则它将正确地将数据加载到组件的状态变量中。但是它将把道具的数据重新加载到组件的状态变量中,并在每个重新渲染周期中擦除所有更改。
 useEffect(() => {
        ...
      }, [order]);
  • mapStateToProps() 是在初始组件安装之后发生的吗?因为这将解释为什么在最初的效果中,道具总是空的。

3。我正在执行的教程:使用挂钩获取数据

我能够使用techniques from this tutorial成功获取数据:

  const [data, setData] = useState({ hits: [] });
  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        "https://hn.algolia.com/api/v1/search?query=redux"
      );
      setData(result.data);
    };
    fetchData();
  }, []);

3 个答案:

答案 0 :(得分:1)

听起来 order 可能是父级每次创建的对象。钩子依赖检查精确匹配 -- previous === next -- 所以即使对象内部的值没有改变,如果对象本身发生变化,效果会再次运行。

最常见的方式是传递普通的对象字面量:

const order = { id: 'foo', SKU: 'bar', cost: 19.95, quantity: 1 };
return <App order={order} />;

这将每次使用新的对象字面量重新填充 order。当您将一个对象分散到另一个对象中时,会发生更阴险的文字重新创建:

const order = { ...oldOrder, quantity: 2 };

这仍然会创建一个新对象。

不要试图改变现有对象。 React 会开始对你做一些非常奇怪的事情。一旦你理解了钩子依赖是如何工作的,改变对象就会让你陷入困境。

您有三种选择:

使用原语作为依赖

如果你能确定什么应该从服务器强制刷新,把它拉到它自己的变量中,并根据它设置你的依赖项。这仅在您只需要向服务器发送有限数量的字段时才有效。

如果在您的示例中,您只需要订单的 id 来进行调用,您可以将其提取到它自己的变量中:

const orderId = order.id;

useEffect(() => {
  const fetchData = async () => {
    const response = await axios(`url-with-id?orderId=${orderId}`);
    setData(response.data);
  };
  fetchData();
}, [orderID]);

请注意,如果您尝试从效果内部访问整个 order 对象,即使您没有将其包含在依赖项数组中,React 也会抱怨,这是有充分理由的。除非你能绝对保证它永远不会改变,除非 orderId 也会改变,否则它可能会导致运行时错误,这些错误稍后会影响到你。仅在效果中使用依赖项数组中的内容。

记住您的对象

如果您的 order 对象非常简单,您可以根据其内容记住它。 useMemo 接受一个依赖数组,与 useEffect 的做法相同,但不是在依赖改变时运行效果,useMemo 在依赖改变时返回一个新对象。

只要依赖项没有改变,useMemo 就会返回相同的对象。这就是为什么它起作用的关键。

如果 order 有几个简单的值,例如 idSKUpricequantity,您可以将对象字面量创建更改为看起来像这样:

const order = useMemo(
  () => ({
    id,
    SKU,
    price,
    quantity
  }),
  [id, SKU, price, quantity]
);

现在,只要 order 中的数字或字符串都没有改变,每次渲染都会得到相同的对象。由于它是同一个对象,useEffect(..., [order]) 将只运行一次。如果这些值之一发生变化,则将提供一个新的 order 对象,useEffect 的依赖数组发生变化,并为新的 order 运行效果。

检查您为何不断获得新的 order 对象

就您而言,您似乎是从 Redux 商店获取 order 对象。 Redux 依赖于其数据仅在必要时更改。每次 Redux 中的数据发生变化时,都会触发每个需要它的组件的变化。如果数据发生不必要的更改,这可能会导致您获得不断更新的对象。

您需要查看是否有另一个组件向 Redux 分派不必要的操作,或者您的 reducer 是否在不需要时意外创建了所有新对象。父母是否在不检查是否需要更新的情况下进行更新?您是否有对数据存储进行大量更新的异步操作(thunk 或 sagas)?在简单地将新数据写入旧数据之前,您是否检查传入数据和现有数据之间的差异?

Redux 状态管理是一个完整的主题。使用 Redux Toolkit 可能比自己动手更容易。它会处理需要在商店中重新创建的内容,而您无需管理不变性问题。如果 FireBase 数据随意覆盖您的数据,Redux Toolkit 可以更轻松地确保只更新在调用之间更改的数据,而不是一直更改所有内容。

答案 1 :(得分:0)

试试这个:

const app = props => {
        const [data, setData] = useState({ results: [] });
          useEffect(() => {
            const getData = async () => {
              const result = await axios('Your URL here');
              setData(result.data);
            };
            getData();
          }, []);

    ...

答案 2 :(得分:0)

试试这个:

const app = props => {
        const [data, setData] = useState({ results: [] });

          const getData = async () => {
              const result = await axios('Your URL here');
              setData(result.data);
            };

          useEffect(() => {
            getData();
          }, []);

          ...
}