Правильный способ сделать один условный вызов функции useFetch в React Hooks? - PullRequest
1 голос
/ 25 сентября 2019

Я построил функцию useFetch, которая после этого тесно смоделирована: https://www.robinwieruch.de/react-hooks-fetch-data Вот ее упрощенная версия: Как правильно вызвать функцию useFetch?

Обратите внимание, что как только выборка инициирована, isLoading устанавливается в true.

У меня есть случай использования, когда вызов выборки должен выходить только для пользователя с правами администратора.Вот код, который я добавил к своему компоненту 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 состоит в том, чтобы сделать вызов выборки только в том случае, если истинно ВСЕ из следующего: 1. Текущий протоколу пользователя есть админ.2. companiesFetch ранее не вызывался.3. newUsers.companies еще не заполнен.

Это кажется логичным, но, тем не менее, если я запускаю этот код, companiesFetch вызывается 25 раз, прежде чем происходит сбой приложения.Я предполагаю, что проблема связана с синхронизацией, а именно то, что companies.isLoading не устанавливается достаточно быстро.

Как бы вы это исправили?

1 Ответ

0 голосов
/ 25 сентября 2019

Основываясь на упрощенном примере, который вы предоставили в ссылке, проблема заключается в 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]);

Inв вашем примере я не уверен, в какой области был объявлен newUsers, но просмотр newUsers.companies = ... в обратном вызове useEffect() вызвал некоторую обеспокоенность, поскольку подразумевается, что ваш функциональный компонент нечист, что может легко привести к незначительным ошибкам..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...