Philip Jebaraj
Philip Jebaraj

Reputation: 203

How to cancel Axios request in react redux with hooks?

I am working in React with Redux. I have written the action called products to fetch the product details from backend using Axios. In that, I used cancel token to cancel the HTTP request if the user navigates to another page during the process.

But still, I got the error in the console like below when I navigate to another page.

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.

When I navigate to the same product page to fetch the data, the request goes to the catch block to throw the error. I used the below code to dispatch the action.

product.js as action

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!" });
    }
  };
};

component file:

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]);

How to resolve this issue?

Upvotes: 1

Views: 2538

Answers (1)

gdh
gdh

Reputation: 13682

You are cancelling the request on unmount. After cancellation, you end up in the catch block of loadProducts. Over there, you are setting the state setError(...). Hence you are getting the error as you are trying to set state on unmounted component.

To solve the issue, simply don't set the state in catch block. As per the your code, there don't seem to be any need of setting error state. If you want you can get it from the store (as the reducer CANCEL_REQUEST should have the message)

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]);

Edit Based on comments:

Make following changes, it works properly.

  • put then block first and then the catch block
  • move setting state in promise-catch block (not try catch)
  • Also, there is no need of try/catch block as we are handling setError in promise-catch block

Working demo

Code snippet

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}
}

Upvotes: 1

Related Questions