如何通过钩子在React Redux中取消Axios请求?

时间:2020-07-08 13:58:25

标签: reactjs react-redux axios

我在Redux中使用React。我编写了一个名为产品的操作,以使用Axios从后端获取产品详细信息。这样,如果用户在此过程中导航到另一个页面,我就使用了取消令牌来取消HTTP请求。

但是,当我导航到另一个页面时,仍然出现如下所示的控制台错误。

index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

当我导航到同一产品页面以获取数据时,请求将转到catch块以引发错误。我使用下面的代码调度操作。

product.js作为操作

const source = Axios.CancelToken.source();
const fetchProducts = () => {
  return async (dispatch) => {
    try {
      const response = await Axios.get("myurl", {
        cancelToken: source.token,
      });

      if (response.status !== 200) {
        throw new Error("Something went wrong, while fetching the products!");
      }

      dispatch({ type: GET_PRODUCTS, products: response.data });
    } catch (err) {
      if (Axios.isCancel(err)) {
        console.log(err.message);
      } else {
        throw err;
      }
    }
  };
};

const cancelRequest = () => {
  return (dispatch) => {
    if (source !== typeof undefined) {
      source.cancel("Operation canceled by the user.");
      dispatch({ type: CANCEL_REQUEST, message: "Request canceled by user!" });
    }
  };
};

组件文件:

const loadProducts = useCallback(async () => {
  setError(null);
  try {
    await dispatch(productActions.fetchProducts());
  } catch (err) {
    setError(err.message);
  }
}, [dispatch, setError]);

useEffect(() => {
  setIsLoading(true);
  loadProducts().then(() => {
    setIsLoading(false);
  });

  return () => {
    dispatch(productActions.cancelRequest());
  };
}, [dispatch, loadProducts, setIsLoading]);

如何解决此问题?

1 个答案:

答案 0 :(得分:1)

您正在unmount上取消请求。取消后,您将进入loadProducts的catch块。在那儿,您正在设置状态setError(...)。因此,当您尝试在未安装的组件上设置状态时,会收到错误消息。

要解决此问题,只需不要在catch块中设置状态。根据您的代码,似乎不需要设置错误状态。如果需要,可以从商店购买(因为减速机CANCEL_REQUEST会显示此消息)

const loadProducts = useCallback(async () => {
  setError(null);
  try {
    await dispatch(productActions.fetchProducts());
  } catch (err) {
    //setError(err.message); //<----- this is the issue, remove this line
  }
}, [dispatch, setError]);

useEffect(() => {
  setIsLoading(true);
  loadProducts().then(() => {
    setIsLoading(false);
  });

  return () => {
    dispatch(productActions.cancelRequest());
  };
}, [dispatch, loadProducts, setIsLoading]);

编辑基于评论:

进行以下更改,即可正常运行。

  • 先放置then块,然后再放置catch
  • 在promise-catch块中移动设置状态(不尝试捕获)
  • 此外,由于我们在promise-catch块中处理setError,因此不需要try / catch块

Working demo

代码段

const useAxiosFetch = url => {
  const [data, setData] = useState(null)
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    let source = axios.CancelToken.source()
    setLoading(true)
    axios
      .get(url, {
        cancelToken: source.token,
      })
      .then(a => {
        console.log('here')
        setData(a)
        setLoading(false)
      })
      .catch(function(thrown) {
        //put catch after then
        if (axios.isCancel(thrown)) {
          console.log(`request cancelled:${thrown.message}`)
        } else {
          console.log('another error happened')
          setData(null)
          setError(thrown)
        }
        // throw (thrown)
      })

    if (source) {
      console.log('source defined')
    } else {
      console.log('source NOT defined')
    }

    return function() {
      console.log('cleanup of useAxiosFetch called')
      if (source) {
        console.log('source in cleanup exists')
      } else {
        source.log('source in cleanup DOES NOT exist')
      }
      source.cancel('Cancelling in cleanup')
    }
  }, [])

  return {data, loading, error}
}