1

我已经构建了一个useFetch与之紧密建模的函数:https ://www.robinwieruch.de/react-hooks-fetch-data这是它的简化版本:如何正确调用 useFetch 函数?

请注意,一旦启动获取,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设置得不够快。

你会如何解决这个问题?

4

1 回答 1

0

根据您在链接中提供的简化示例,问题是在第一次渲染后无条件调用useEffect()的实现内部。鉴于您的用例,您不需要这种行为,因为您是根据您列举的条件手动调用它的。useFetch()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声明,但newUsers.companies = ...useEffect()回调中看到引起了一些担忧,因为这意味着您的功能组件不纯,这很容易导致细微的错误。

于 2019-09-25T19:45:53.780 回答