Reputation: 1103
The Abortcontroller signal is not working for me with Axios in React.
I wanted to replace CancelToken (as it's deprecated) with the AbortController, but it is not working, respectively the requests are not being canceled.
let testController: AbortController;
function loadTest() {
testController = new AbortController();
TestAPI.getTest(testController.signal)
.then((e) => {
console.log(e.data);
})
.catch((e) => {
console.error(e);
});
}
Also in the UseEffect Cleanup I do this (here it should cancel) and also the signal's state is set to aborted, but still the request is not canceled:
useEffect(() => () => {
if (testController) testController.abort();
// console.log(testController.signal.aborted) => **true**
}, []);
Here is my API, where I pass the AbortSignal to the request:
getTest(signal?: AbortSignal): Promise<AxiosResponse<Test[]>> {
return axios.get(`${URI}/test`, { signal });
},
When using Axios.CancelToken.source was working fine, but now with the AbortController, the request is never canceled.
Using: "axios": "^0.26.0",
Did someone manage to integrate the AbortController with React and Axios? Or does the AbortController only work with fetch?
Upvotes: 19
Views: 57450
Reputation: 203476
According to the docs axios supports the AbortController
of the fetch API
.
Axios supports AbortController to abort requests in fetch API way:
const controller = new AbortController(); axios.get('/foo/bar', { signal: controller.signal }).then(function(response) { //... }); // cancel the request controller.abort()
It's not clear exactly where testController
is declared:
let testController: AbortController;
but I suspect it's in the body of the function component and redeclared on a subsequent render cycle.
I suggest using a React ref to store an AbortController
, and reference this ref value around your app. This is so the component holds on to a stable reference of the controller from render cycle to render cycle, to be referenced in any useEffect
hook cleanup function to cancel in-flight requests if/when the component unmounts.
const abortControllerRef = useRef<AbortController>(new AbortController());
function loadTest() {
TestAPI.getTest(abortControllerRef.current.signal)
.then((e) => {
console.log(e.data);
})
.catch((e) => {
console.error(e);
});
}
useEffect(() => {
const controller = abortControllerRef.current;
return () => {
controller.abort();
};
}, []);
Upvotes: 31
Reputation: 623
Here is another way if you are not using useEffect() with fetchClick dependencies instead using a button to upload and cancel the progress. Thanks to Drew for giving idea of using useRef which helped me.
const controller = useRef(null); // Change to useRef to maintain the same controller across renderscode here
const handleUpload = () => {
controller.current = new AbortController(); // Create a new AbortController for each upload
const signal = controller.current.signal; // Get the signal from the current controller
axios.post("/upload",{ signal: signal }).then(res => console.log(res.data)).catch((err) => console.log(err.message)));
const handleCancelUpload = () => {
if (controller.current) {
controller.current.abort(); // Abort the current request if controller exists
}};
Upvotes: 1
Reputation: 1224
I would recommend to read this post.
In a nutshell you would like to use useEffect
to create controller, and, what is more important, to use return
statement to abort the controller.
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
getData(signal)
//cleanup function
return () => {controller.abort();};
}, [fetchClick]);
getData
function can then be your axios
call in the form:
const getData = async (signal) =>{
const res = await axios.get(url, {signal: signal}).then(...)
}
Upvotes: 8
Reputation: 697
Here, I created a common Axios interceptor with AbortController.
import axios from 'axios';
const instance = axios.create({
timeout: 25000,
params: {},
});
/* Store requests */
const sourceRequest: Record<string, any> = {};
const controller = new AbortController();
const timeoutInterceptor = instance.interceptors.request.use(
async (request: any) => {
/* If the application exists cancel */
if (sourceRequest[request.url]) {
request.cancelToken = controller.signal;
}
return request;
},
error => {
return Promise.reject(error);
},
);
// Set a timeout to cancel the request
setTimeout(() => {
instance.interceptors.request.eject(timeoutInterceptor);
controller.abort();
}, 5000);
export const apiService = {
request(config = {}) {
return instance.request(config);
},
getData(url: string, config = {}) {
return instance.get(url, config);
},
postData(url: string, data?: any, config?: Record<string, any>) {
return instance.post(url, data, config);
},
putData(url: string, data?: any, config?: Record<string, any>) {
return instance.put(url, data, config);
},
patchData(url: string, data?: any) {
return instance.patch(url, data);
},
deleteData(url: string, config = {}) {
return instance.delete(url, config);
},
};
Upvotes: 1
Reputation: 31
Abort controller often use in useEffect
to fetch some data. So, in order to implement the control you can try this:
//...
const [data, setData] = useState([]);
useEffect(() => {
const controller = new AbortController();
axios
.get("https://somedata.com", { signal: controller.signal })
.then(res => {
setData(res.data);
})
.catch(err => console.log(err));
// return cleanup function to abort request
return () => {
controller.abort();
};
}, []);
//...
Upvotes: 2
Reputation: 1610
There's my code example, hope this helps:
useEffect(() => {
const abortController = new AbortController();
const getData = async () => {
try {
const res = await axios("/api/data/", {
signal: abortController.signal,
});
const data = res.data
} catch (error) {
if (error.name !== "CanceledError") {
/* Logic for non-aborted error handling goes here. */
console.log('error:', error)
}
}
};
getData();
// clean up function when unmounted to avoid getData fired twice problem in React 18
return () => abortController.abort();
}, []);
Upvotes: 1
Reputation: 1
All you need regarding AbortController with axios here
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
Upvotes: -4