【问题标题】:Correct way to make single conditional call to React Hooks' useFetch function?对 React Hooks 的 useFetch 函数进行单一条件调用的正确方法?
【发布时间】:2020-01-25 19:11:17
【问题描述】:

我已经构建了一个 useFetch 函数,它在此之后紧密建模:https://www.robinwieruch.de/react-hooks-fetch-data 这是它的简化版本:How to correctly call useFetch function?

请注意,一旦启动提取,isLoading 将设置为 true。

我有一个用例,只有管理员用户才需要发出 fetch 调用。这是我添加到我的 React 组件中的代码:

const [companies, companiesFetch] = useFetch(null, {});

if (appStore.currentAccessLevel === 99 && !companies.isLoading && newUsers.companies.length === 0) {
  console.log('About to call companiesFetch');
  companiesFetch(`${API_ROOT()}acct_mgmt/companies`);
}

useEffect(() => {
  if (!companies.isLoading && companies.status === 200) {
    newUsers.companies = companies.data.companies;
  }
}, [companies.isLoading, companies.status, newUsers.companies, companies.data]);

第一个 if 语句的想法是仅在以下所有条件都为真时才进行 fetch 调用: 1.当前登录的用户是管理员。 2. companiesFetch 之前没有被调用过。 3. newUsers.companies 尚未填充。

这似乎合乎逻辑,但如果我运行此代码,companiesFetch 会在应用程序崩溃之前被调用 25 次。我认为问题是时间问题,即companies.isLoading 设置得不够快。

你会如何解决这个问题?

【问题讨论】:

  • 你能分享companiesFetch的功能吗?如果有 fetch 不应该在 useEffect 内吗?
  • @Alvaro 我已经修改了我的问题,以向您展示我是如何定义它的。我还提供了另一个帖子的链接,其中显示了我的实际 useFetch 函数的简化版本。
  • 为什么必须将`${API_ROOT()}acct_mgmt/companies` 传递给钩子和对companiesFetch() 的调用?
  • @PatrickRoberts 对此感到抱歉。我已经编辑了它的实际情况。第一行代码简单地定义了一个useFetch 的实例来获取公司的数据。然后在if 子句中进行实际提取。我的问题实际上是关于为什么我在第一个 if 子句中检查的内容不足以阻止第二次输入该子句。换句话说,你会在第一个if 中检查什么,只调用一次companiesFetch

标签: javascript reactjs asynchronous react-hooks use-effect


【解决方案1】:

根据您在链接中提供的简化示例,问题在于useFetch() 的实现中的useEffect() 在第一次渲染后无条件地调用fetchData()。鉴于您的用例,您不需要这种行为,因为您是根据您列举的条件手动调用它。

除此之外,您还通过直接在功能组件中调用 companiesFetch() 加剧了问题,这是您不应该这样做的,因为 fetchData() 会同步调用 setState() 并在运行期间修改组件的状态渲染。

您可以通过首先修改useFetch() 以在第一次渲染后删除无条件获取来解决这些问题,然后将条件逻辑移动到组件内的useEffect() 回调中。以下是我如何实现useFetch()

const initialFetchState = { isLoading: false, isError: false };

// automatically merge update objects into state
const useLegacyState = initialState => useReducer(
  (state, update) => ({ ...state, ...update }),
  initialState
);

export const useFetch = config => {
  const instance = useMemo(() => axios.create(config), [config]);
  const [state, setState] = useLegacyState(initialFetchState);
  const latestRef = useRef();
  const fetch = useCallback(async (...args) => {
    try {
      // keep ref for most recent call to fetch()
      const current = latestRef.current = Symbol();

      setState({ isError: false, isLoading: true });

      const { data } = await instance.get(...args).finally(() => {
        // cancel if fetch() was called again before this settled
        if (current !== latestRef.current) {
          return Promise.reject(new Error('cancelled by later call'));
        }
      });

      setState({ data });
    } catch (error) {
      if (current === latestRef.current) {
        setState({ isError: true });
      }
    } finally {
      if (current === latestRef.current) {
        setState({ isLoading: false });
      }
    }
  }, [instance]);

  return [state, fetch];
};

这是我在useEffect() 中调用它的方式,以防止同步调用setState()

const [companies, companiesFetch] = useFetch({ baseURL: API_ROOT() });
const [newUsers, setNewUsers] = useState({ companies: [] });

useEffect(() => {
  if (!companies.isLoading) {
    if (appStore.currentAccessLevel === 99 && newUsers.companies.length === 0) {
      console.log('About to call companiesFetch');
      companiesFetch('acct_mgmt/companies');
    } else if (companies.status === 200 && newUsers.companies !== companies.data.companies) {
      setNewUsers({ companies: companies.data.companies });
    }
  }
}, [appStore.currentAccessLevel, companies, newUsers]);

在您的示例中,我不确定newUsers 的声明范围是什么,但是在useEffect() 回调中看到newUsers.companies = ... 引起了一些担忧,因为这意味着您的功能组件不纯,这很容易导致细微的错误。

【讨论】:

  • 一个直接的问题:假设我想使用您的useFetch 来检查用户名或电子邮件地址,或者在输入后立即进行检查。考虑到您的useFetch 包含在useEffect 中,如何做到这一点?此外,您说不应该直接在功能组件中调用useFetch。我见过的所有示例代码都是这样做的,例如:robinwieruch.de/react-hooks-fetch-data 查看“REDUCER HOOK FOR DATA FETCHING”上方的代码。
  • @robertwerner-goodyear 您引用的示例调用fetchData,只要调用setUrl(别名doFetch)的值与上一次调用不同,并且仅调用doFetch()来自onSubmit 处理程序,所以不,在渲染阶段不会调用它。至于initialUrl触发的调用,反正是被useEffect()回调延迟到渲染阶段之后,所以还是不同步的。
  • @robertwerner-goodyear 顺便说一句,我不是该博客文章中提供的示例代码的忠实粉丝。只要有并发的 axios 请求挂起,在自定义钩子中对 setState() 的异步调用就会导致组件状态的竞争条件。
  • 是的,明白了。嘿,我试图私下联系你。有什么好的方法吗?
  • @robertwerner-goodyear “试过”?我不想接受来自 Stack Overflow 的私人通信,但如果你愿意,可以给我发消息 here
猜你喜欢
  • 1970-01-01
  • 2021-12-07
  • 2021-03-23
  • 1970-01-01
  • 2020-01-15
  • 2019-10-17
  • 1970-01-01
  • 1970-01-01
  • 2021-10-24
相关资源
最近更新 更多