Reputation: 203
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
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.
then
block first and then the catch
blocksetError
in promise-catch blockCode 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