Canceling Axios and prevent state update.
In this article i would like to show you how we can cancel a Axios Api requests. Now why would we want to do that!? Well their could be a couple of reasons like saving our users bandwidth, but in this example lets focus on another reason.
Let's say we make a Api request and want to update the components state with the response data. When the user for some reason moves away from our component, when the request isn't finished, the request will try and update the no longer existent component state. And this will result in React yelling at us that we made a mistake become this is not allowed.
Automatically canceling the Axios request when the component unmounts will prevent this error and the screaming in our console.
Building a little test case.
First we have to make a little test case app. The example below is pretty silly, but you can compare it with a app that has a react router, where our user is frantically clicking between different menu items, mounting and unmounting our components.
import React, { useState } from "react";
import ComponentWithRequest from "./components/ComponentWithRequest";
const App = () => {
const [mounted, setMounted] = useState(true);
return (
<div className="App">
{mounted && (
<ComponentWithRequest
toggleMounted={() => {
setMounted(!mounted);
}}
/>
)}
</div>
);
};
export default App;
Here we create a App component that has a mounted state that initializes to true. As long as mounted is true we render a ComponentWithRequest component and we pass it a toggleMounted prop function that when called toggles the mounted state to false using the setMounted function. And that will unmount the ComponentWithRequest.
The ComponentWithRequest component
import React, {useEffect} from "react";
const ComponentWithRequest = props => {
useEffect(() => {
props.toggleMounted();
}, [props]);
return (
<div className="box">
<p>I will immediately get unmounted.</p>
</div>
);
};
export default ComponentWithRequest;
Inside the ComponentWithRequest component we have a useEffect hook that runs when the component gets rendered. Inside the useEffect we call the toggleMounted function we received as a prop.
So all of this will mount the ComponentWithRequest when our app runs, and then it will immediately unmount it again simulating the user that is clicking around moving away from out component.
Making the Axios request.
Now we can add the Axios request to see what happens.
import React, {useState, useEffect} from "react";
import axios from "axios";
const ComponentWithRequest = props => {
const [apiData, setApiData] = useState(null);
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/todos").then(response => {
setApiData(response.data);
});
props.toggleMounted();
}, [props]);
return (
<div className="box">
<p>I will immediately get unmounted.</p>
</div>
);
};
export default ComponentWithRequest;
In the code above we add a apiData state using the useState hook. And in the useEffect function we use Axios to make a request to the JSONPlaceholder api.
After we start the request we call the toggleMounted function, effectively unmounting the component. Now the Axios request continues none the less and when it receives it's response it will try to update the component's no longer existing apiData state. resulting in the nasty warning shown in the image below.
Auto cancel the request when component unmounts.
Let's fix the problem by changing our code to auto cancel the Axios request when our component unmounts.
import React, {useState, useEffect} from "react";
import axios from "axios";
const ComponentWithRequest = props => {
const [apiData, setApiData] = useState(null);
useEffect(() => {
const source = axios.CancelToken.source();
axios.get("https://jsonplaceholder.typicode.com/todos", {
cancelToken: source.token
}).then(response => {
setApiData(response.data);
}).catch(err => {
console.log("Catched error: " + e.message);
});
props.toggleMounted();
return () => {
source.cancel("Component got unmounted");
};
}, [props]);
return (
<div className="box">
<p>I will immediately get unmounted.</p>
</div>
);
};
export default ComponentWithRequest;
Inside the useEffect function we now first grab a CancelToken Source from Axios and store it in a constant named source. We can use this to get a CancelToken and actually cancel the request.
Next we pass a config object to our Axios request setting the cancelToken key to a token we get from the source object.
Now we have to add a catch block to our request because canceling the request will throw an error, and if we don't catch that we will have a new bug on our hands. So we catch the error and simply log out the error message.
Finally we return a function from the useEffect function. This function gets called when our component unmounts. Inside this function we call the cancel method on our source object and we pass it a little message saying why the request got canceled. This message is passed to the error object in our catch block, so that is what will get logged out there.
Now when our component gets unmounted the Axios Http request gets canceled before it tries to update the component state. And as you can see in the image above, we now get a friendly log message instead of that nasty warning we got before.
CodeSandBox
If you want to play around with the code i have set up a CodeSandBox for you to have a look at. You can comment out the call to source.cancel() on line 31 of the ComponentWithRequest.js file and refresh the little browser to see for yourself what happens if we don't cancel the request.
Follow?
Let's connect on twitter @Vanaf1979, on Dev.to @Vanaf1979 or here on Hashnode so i can notify you about new articles, and other web development related resources.
Originally posted on my website on June 17th 2020
Thanks for reading and stay safe.