卸载组件时禁用customHook

时间:2020-01-27 15:32:47

标签: reactjs async-await react-hooks

我的控制台出现此错误: “无法在已卸载的组件上执行React状态更新。这是无操作,但表明您的应用程序中存在内存泄漏。要修复,取消useEffect清理功能中的所有订阅和异步任务。”

这是因为我的组件正在调用带有内部异步调用的customHook ...,但是在调用返回我需要的值之前,组件本身已卸载。

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 个答案:

答案 0 :(得分:1)

这是我对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