Отключить customHook, когда компонент отключен - PullRequest
0 голосов
/ 27 января 2020

У меня ошибка в консоли: «Невозможно выполнить обновление состояния React для неустановленного компонента. Это неоперация, но это указывает на утечку памяти в вашем приложении. Чтобы исправить, отмените все подписки и асинхронные задачи в функции очистки useEffect. "

Это потому, что мой компонент вызывает customHook с асинхронным вызовом c внутри ... но сам компонент размонтируется до того, как вызов вернет нужное мне значение.

const ResolveData = ({ dispatch }) => {
  const [loading, error] = setPermission(dispatch);

  useEffect(() => {
    return () => {
      console.log("canceled");
    };
  }, []);

  return ...;
};

Мой хук "setPermission" отправляет значение, и это значение вызывает повторную загрузку компонента (useReducer находится в отце). Когда компонент перезагружается, он снова вызывает customHook, но компонент размонтируется ... По крайней мере, это то, что я понял о проблеме.

Этот компонент перенаправляет меня на другой компонент, используя переменную возвращено customHook.

Это мой customHook

export default dispatch => {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);

  useEffect(() => {
    const getUserPermission = async () => {
      try {
        setLoading(true);
        const response = await requests.getData("/url"); //my URL
        dispatch({
          type: "dispatch_type",
          payload: response.data.role
        });
      } catch (err) {
        if (axios.isCancel(err)) {
          console.log(err);
        }
        setError(true);
      } finally {
        setLoading(false);
      }
    };
    if (getToken()) {
      getUserPermission();
    }
    return () => {
      const cancelToken = createCancelToken();
      cancelToken.cancel("canceled");
    };
  }, [dispatch]);

  return [loading, error];
};

Любая помощь о том, как отключить вызов customHook, когда компонент отключен?

1 Ответ

1 голос
/ 27 января 2020

Вот как выглядит моя реализация useApi:

// libs
import axios from 'axios';
import { useReducer, useRef } from 'react';

export const actionTypes = {
  SET_LOADING: 'SET_LOADING',
  SET_DATA: 'SET_DATA',
  SET_ERROR: 'SET_ERROR',
};

export const fetchData = async (dispatch, cancelToken, action, params) => {
  dispatch({ type: actionTypes.SET_LOADING });

  return action(params, cancelToken)
    .then(response => response.data)
    .then(payload => {
      dispatch({ type: actionTypes.SET_DATA, payload });
      return payload;
    })
    .catch(error => {
      if (!axios.isCancel(error)) {
        dispatch({ type: actionTypes.SET_ERROR, error });
        throw error;
      }
    });
};

const initialState = { isLoading: false, payload: {}, error: null };

export const reducer = (state, action) => {
  const { type, payload, error } = action;

  switch (type) {
    case actionTypes.SET_LOADING:
      return { ...state, isLoading: true, error: null };
    case actionTypes.SET_DATA:
      return { ...state, isLoading: false, error: null, payload };
    case actionTypes.SET_ERROR:
      return { ...state, isLoading: false, error };
    default:
      return state;
  }
};

/**
 * Reusable hook to make api calls using a function (action).
 * It handles cancellation of previous requests automatically.
 *
 * @typedef State
 * @type {object}
 * @property {object} payload - Api response.
 * @property {boolean} isLoading - status of Api call.
 * @property {object} error - error object in case of failed call.
 *
 * @typedef ExecuteAction
 * @type {function}
 * @param {object} params - params to pass to action
 * @returns {Promise} - resolves with payload
 *
 * @typedef useApi
 * @param {function} action
 * @returns [State, ExecuteAction, cleanupAction]
 */
const useApi = action => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const axiosSource = useRef(null);
  const cleanupAction = () => {
    if (axiosSource.current) {
      axiosSource.current.cancel('Cleaned up previous request.');
    }
  };
  const executeAction = (params = {}) => {
    cleanupAction();
    axiosSource.current = axios.CancelToken.source();
    return fetchData(dispatch, axiosSource.current.token, action, params);
  };

  if (!action || typeof action !== 'function') {
    throw Error('Missing action || type of action is not function.');
  }

  return [state, executeAction, cleanupAction];
};

export default useApi;

Вы можете использовать ее в своем компоненте следующим образом:

const getData = (params, cancelToken) => axios.post('some url', params, { cancelToken });

const SomeComponent = () => {
  const [state, fetchData, cleanup] = useApi(getData);
  const { isLoading, error, payload } = state;

  useEffect(() => {
   fetchData({ key: 'some params to pass to getData action' });
   return cleanup;
  })
}

Вы можете настроить useApi как вам как.

...