1

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?

Petrogad
  • 4,405
  • 5
  • 38
  • 77
Philip Jebaraj
  • 183
  • 2
  • 2
  • 8

1 Answers1

1

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}
}
gdh
  • 13,114
  • 2
  • 16
  • 28
  • Still, it is not worked. I removed the setError method. But no luck, I got same error. – Philip Jebaraj Jul 08 '20 at 14:59
  • Whenever I navigate to the same page to fetch the data, it goes to the catch block in action. catch (err) { if (Axios.isCancel(err)) { console.log(err.message); } else { throw err; } } – Philip Jebaraj Jul 08 '20 at 15:06
  • hi - it should work... can you share your full code if possible (codesandbox or github) ... also [see an example here](https://stackoverflow.com/questions/53861916/canceling-an-axios-rest-call-in-react-hooks-useeffects-cleanup-failing) – gdh Jul 09 '20 at 00:34
  • Hi, Please find the GitHub path to find the source. https://github.com/spvjebaraj/react-redux-axios-cancel – Philip Jebaraj Jul 09 '20 at 05:55
  • I have also tested this scenario in code sandbox at https://codesandbox.io/s/8lzwl57nr8?file=/src/index.js. It also fails. – Philip Jebaraj Jul 09 '20 at 06:07
  • sorry for delay - i was busy --- updated the answer now ... hope all good now.. – gdh Jul 13 '20 at 06:02
  • Thanks, gdh. It is working fine without any issues if I use it in the component. How to run this code from reducer? – Philip Jebaraj Jul 13 '20 at 09:16
  • glad to hear it is working ... reg reducer, could you please raise a new qn as your current issue is resolved and it is slightly different topic... asking good qns is good for your reputations as well and it catches eyes of others – gdh Jul 13 '20 at 09:43