Cancel axios get request when typing reactjs - reactjs

I'm creating an autocomplete function, so basically whenever I type on search box it will cancel the previous http get request. I checked the jQuery ui autocomplete and that's what they did. Is that possible in axios if yes how can I implement it. So far here's my code for http get request:
export function autocompleteSearchTest(value){
return axios.get(`https://jqueryui.com/resources/demos/autocomplete/search.php`,{
params: {
q: value
}
})
.then(response => {
return response.data.response;
})
.catch(error => {
const result = error.response;
return Promise.reject(result);
});
}
Here's the screenshot when I type on the search box:
as you can see it doesn't cancel the previous http get request.

Since axios v0.15 you can cancel request:
You can also create a cancel token by passing an executor function to
the CancelToken constructor:
var CancelToken = axios.CancelToken;
var cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
})
});
// cancel the request
cancel();
For more information please have a look Cancellation. And according to your code:
import React from 'react'
import axios from 'axios';
export function autocompleteSearchTest(value) {
if (cancel != undefined) {
cancel();
}
return axios.get(`https://jqueryui.com/resources/demos/autocomplete/search.php`, {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
}),
params: {
q: value
}
})
.then(response => {
return response.data.response;
})
.catch(error => {
const result = error.response;
return Promise.reject(result);
});
}
var CancelToken = axios.CancelToken;
var cancel;
export class AutoComplete extends React.Component {
constructor() {
super()
this.state = {
search: ''
};
this.handleSearchChange = this.handleSearchChange.bind(this);
}
handleSearchChange(event) {
const target = event.target;
const value = target.value;
this.setState({
search: value
});
autocompleteSearchTest(value)
}
render() {
return (
<div className="App-intro Dialog-section">
<h2>AutoComplete</h2>
<div className="form-control">
<label htmlFor="password">Lookup :</label>
<input name="search" type="text" value={this.state.search}
onChange={this.handleSearchChange}
id="password" ref="password" placeholder="Enter line"/>
</div>
</div>
)
}
}
export default AutoComplete;
And here it's

use: react, axios and redux-axios-middleware
user actions:
import axios from 'axios';
import * as type from 'constants/user';
const CancelToken = axios.CancelToken;
let cancel;
export const addUser = (data) => {
if (cancel !== undefined) cancel();
return ({
types: [type.ADD_USER_REQUEST, type.ADD_USER_SUCCESS, type.ADD_USER_FAILURE],
payload: {
request: {
url: '/api/user',
cancelToken: new CancelToken(c => cancel = c),
method: 'POST',
data,
},
},
});
};

You can create such a small helper wrapper and use it anywhere where you need to cancel previous request:
// ../services.js
function createApi(baseURL) {
const axiosInst = axios.create({
baseURL,
});
axiosInst.defaults.headers.common['Content-Type'] = 'application/json';
return (type = 'get') => {
let call = null;
return (url, data, config) => {
if (call) {
call.cancel('Only one request allowed!');
}
call = axios.CancelToken.source();
const extConf = {
cancelToken: call.token,
...config,
};
switch (type) {
case 'request':
return axiosInst[type](extConf);
case 'get':
case 'delete':
case 'head':
return axiosInst[type](url, extConf);
default:
return axiosInst[type](url, data, extConf);
}
};
};
}
export const baseApi = createApi('http://localhost:5000/api/')
And then use it anywhere like this:
import baseApi from '../servises.js';
const fetchItemsService = baseApi('post');
//...
componentDidMount() {
fetchItemsService('/items').then(() => {});
}
//...

You wanna use something like RxJS for this, then you can delay the input if the users search, before the requests are made.

use faxios
// building..
let req = faxios()
.baseURL("http://jsonplaceholder.typicode.com")
.url("posts", 1, "comments")
// fetching..
req // =>
.FETCH // => Promise
.then(res => {})
.catch(err => {});
// canceling...
req.cancel();

Based on your code, you can cancel token using axios with this
export function autocompleteSearchTest(value){
const cancelToken = axios.CancelToken.source()// 1st
return axios.get(`https://jqueryui.com/resources/demos/autocomplete/search.php`,{
params: {
q: value
}
})
.then(response => {
return response.data.response;
})
.catch(error => {
if (axios.isCancel(e)) return //3rd
const result = error.response;
return Promise.reject(result);
});
return () =>{ // 2nd
cancelToken.cancel();
}
}

Related

How to logout automatically when session expires while using createAsyncThunk and axios (withcredential) option using react and redux toolkit?

I am trying to logout the user when the session expires after a certain period of time. I am using redux-toolkit with react for my API calls and, hence, using the createAsyncThunk middleware for doing so.
I have around 60 API calls made in maybe 20 slices throughout my application. Also, there is a async function for logout too that is fired up on the button click. Now the problem that I am facing is that if the session expires, I am not able to logout the user automatically. If I had to give him the message, then I had to take up that message from every api call and make sure that every screen of mine has a logic to notify the Unautherised message.
I did check a method called Polling that calls an API after a certain given time. And I believe that this is not a very efficient way to handle this problem.
**Here is a little code that will help you understand how my API calls are being made in the slices of my application. **
// Here is the custom created api that has axios and withcredentials value
import axios from "axios";
const api = axios.create({
baseURL:
process.env.NODE_ENV === "development" ? process.env.REACT_APP_BASEURL : "",
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
export default api;
// My Logout Function!!
export const logoutUser = createAsyncThunk(
"userSlice/logoutUser",
async (thunkAPI) => {
try {
const response = await api.get("/api/admin/logout");
if (response.status === 200) {
return response.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
return thunkAPI.rejectWithValue(e.response.data);
}
}
);
I want to dispatch this function whenever there is a response status-code is 401 - Unauthorised. But I don't want to keep redundant code for all my other API calls calling this function. If there is a middleware that might help handle this, that would be great, or any solution will be fine.
// Rest of the APIs are called in this way.
..........
export const getStatus = createAsyncThunk(
"orgStat/getStatus",
async (thunkAPI) => {
try {
const response = await api.get("/api/admin/orgstat");
if (response.status === 200) {
return response.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
return thunkAPI.rejectWithValue(e.response.data);
}
}
);
const OrgStatusSlice = createSlice({
name: "orgStat",
initialState,
reducers: {
.......
},
extraReducers: {
[getStatus.pending]: (state) => {
state.isFetching = true;
},
[getStatus.rejected]: (state, { payload }) => {
state.isFetching = false;
state.isError = true;
state.isMessage = payload.message;
},
[getStatus.fulfilled]: (state, { payload }) => {
state.isFetching = false;
state.data = payload.data;
},
},
});
.......
If needed any more clearence please comment I will edit the post with the same.
Thank You!!
import axios from 'axios'
import errorParser from '../services/errorParser'
import toast from 'react-hot-toast'
import {BaseQueryFn} from '#reduxjs/toolkit/query'
import {baseQueryType} from './apiService/types/types'
import store from './store'
import {handleAuth} from './common/commonSlice'
import storageService from '#services/storageService'
// let controller = new AbortController()
export const axiosBaseQuery =
(
{baseUrl}: {baseUrl: string} = {baseUrl: ''}
): BaseQueryFn<baseQueryType, unknown, unknown> =>
async ({url, method, data, csrf, params}) => {
const API = axios.create({
baseURL: baseUrl,
})
API.interceptors.response.use(
(res) => {
if (
res.data?.responseCode === 1023 ||
res.data?.responseCode === 6023
) {
if(res.data?.responseCode === 1023){
console.log('session expired')
store.dispatch(handleSession(false))
return
}
console.log('Lopgged in somewhere else')
store.dispatch(handleSession(false))
storageService.clearStorage()
// store.dispatch(baseSliceWithTags.util.resetApiState())
return
// }, 1000)
}
return res
},
(error) => {
const expectedError =
error.response?.status >= 400 &&
error.response?.status < 500
if (!expectedError) {
if (error?.message !== 'canceled') {
toast.error('An unexpected error occurrred.')
}
}
if (error.response?.status === 401) {
// Storage.clearJWTToken();
// window.location.assign('/')
}
return Promise.reject(error)
}
)
try {
let headers = {}
if (csrf) headers = {...csrf}
const result = await API({
url: url,
method,
data,
headers,
params: params ? params : '',
baseURL: baseUrl,
// signal: controller.signal,
})
return {data: result.data}
} catch (axiosError) {
const err: any = axiosError
return {
error: {
status: errorParser.parseError(err.response?.status),
data: err.response?.data,
},
}
}
}
I am also using RTK with Axios. You can refer to the attached image.

Return the fetch response from another file

I am trying to call a function that calls fetch to an API from a React component in a separate file and am not finding the correct solution to get the correct response back.
When I debug, the result returns before the updateAccount function has completed and the final result is never returned to my update function.
Inside the fetch, the API returns the correct response whether it is successful or has validation errors and those results are correctly assigned to result.success and result.errors but the result doesn't get returned from the function so that the caller can make use of those values.
Inside of my React component:
import { updateAccount } from '../services/requests';
...
const update = (account: EditAccountModel) => {
const result = updateAccount(account);
if(result.errors.length > 0) {
// will notify of errors
console.log(result.errors); // is an empty array instead of validation errors
} else {
// will notify of success
console.log(result.success); // is an empty string instead of success message
}
}
...
My request file
export const updateAccount = (account: EditAccountModel | undefined): EditAccountResponseModel => {
const result = new EditAccountResponseModel();
fetch(baseUrl, {
method: 'PUT',
body: JSON.stringify(account),
headers
})
.then(response => {
if (!response.ok) {
return Promise.reject(response);
}
result.success = `${account?.name} was updated successfully!`
})
.catch(error => {
if (typeof error.json === "function") {
error.json().then(jsonError => {
result.errors.push(jsonError);
}).catch(genericError => {
result.errors.push(genericError);
});
}
});
return result;
}
The result reassignment happens inside then catch but it won’t be affective in the way you expected. The guaranteed way to return correct result is via a callback() passed to your updateAccount() if you could afford it:
export const updateAccount = (
account: EditAccountModel | undefined,
callback: Function
): EditAccountResponseModel => {
const result = new EditAccountResponseModel();
fetch(baseUrl, {
method: 'PUT',
body: JSON.stringify(account),
headers
})
.then(response => {
if (!response.ok) {
return Promise.reject(response);
}
result.success = `${account?.name} was updated successfully!`
callback(result);
})
.catch(error => {
if (typeof error.json === "function") {
error.json().then(jsonError => {
result.errors.push(jsonError);
callback(result);
}).catch(genericError => {
result.errors.push(genericError);
callback(result);
});
}
});
}
And inside your React component:
const update = (account: EditAccountModel) => {
const handleResult = (res) => {
// your result callback code
// ...
};
updateAccount(account, handleResult);
// ...
}
Alternative way that keeps your current structure is to change your current updateAccount() to an async function, then return await fetch().
You need to wait for the response . I'll let read more about how Promise work in JavaScript.
I wouldn't code updateAccount the same way you did, especially where you use the variable result and update it inside the flow of the promise (you really don't need that). You're also using React so you can use the state to store and update the result of the update function. But let's fix your problem first:
export const updateAccount = async (account: EditAccountModel | undefined): EditAccountResponseModel => {
const result = new EditAccountResponseModel();
await fetch(baseUrl, {
method: 'PUT',
body: JSON.stringify(account),
headers
})
.then(response => {
if (!response.ok) {
return Promise.reject(response);
}
result.success = `${account?.name} was updated successfully!`
})
.catch(error => {
if (typeof error.json === "function") {
error.json().then(jsonError => {
result.errors.push(jsonError);
}).catch(genericError => {
result.errors.push(genericError);
});
}
});
return result;
}
First make your function updateAccount async then await the result of the promise.
Now the same thing for the function update:
const update = async (account: EditAccountModel) => {
const result = await updateAccount(account);
if(result.errors.length > 0) {
// will notify of errors
} else {
// will notify of success
}
}

How to chain promise to the return of API call using redux-pack in a React and Redux app?

I'm getting the error .then() is not a function while attempting to write a React app test w/ jest/enzyme. I don't think the test code is the problem, but for reference, I am following this Redux example.
Here's the code that throws the error:
return store.dispatch(actions.fetchFiles()).then(() => {
expect(store.getActions()).toEqual(expectedActions)
});
My client's codebase uses redux-pack and some conventions that I am not familiar with and I'm having a hard time deciphering where the actual promise is being executed and thus how to chain a "then" function when calling it from my test code as shown in the redux example link posted above.
I've tried to do some debug logging and some variations on the syntax, for example, I attempted to call .then directly on actions.fetchFiles() since I was under the impression that it was the call that actually returned the promise, but it didn't work. I'm getting a tad lost in all this code and questioning where the promise is actually getting executed/returned.
The basic structure of the code is as follows:
A connected Container component that fetches a list of files from an API and then dispatches.
The actual page works fine, but my attempts to test (like the Redux article referenced above) blow up.
Here are what I believe to be the relevant blocks of code in play:
Container component
componentDidMount() {
const { actions } = this.props;
actions.fetchUpload();
}
const mapStateToProps = state => ({
...state.upload,
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators({
...uploadActions,
}, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(UploadContainer);
actions.js
import api from '../../core/api';
export const FETCH_FILES = 'upload/fetch-files';
const actions = {
fetchFiles: () => ({
type: FETCH_FILES,
promise: api.upload.getFiles()
})
};
actions.fetchUpload = () => (dispatch) => {
dispatch(actions.fetchFiles());
};
export default actions;
reducer.js
import { handle } from 'redux-pack';
import { FETCH_FILES } from './actions';
const initialState = {
files: []
};
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_FILES:
return handle(state, action, { // this is redux-pack syntax
success: (s, a) => ({ ...s, files: a.payload.files })
});
default:
return state;
}
};
upload.js (api.upload.getFiles)
export default http => ({
getFiles: () => http.get('/file')
});
api.js - uses Axios
import Axios from 'axios';
import { SubmissionError } from 'redux-form';
import queryString from 'query-string';
import upload from './upload';
const axios = Axios.create({
baseURL: '/api/',
withCredentials: true,
paramsSerializer: params => queryString.stringify(params),
});
class HttpError extends Error {
constructor(status, error, errors = {}) {
super(`Http Error: ${status}`);
this.status = status;
this.error = error;
this.errors = errors;
}
getReduxFormError = defaultError => new SubmissionError({
_error: this.error || defaultError,
...this.errors,
});
}
const handleUnauth = (method, url, options) => (err) => {
const { status } = err.response;
if (status === 401) {
return axios.get('/users/refresh')
.then(() => method(url, options))
.catch(() => Promise.reject(err));
}
return Promise.reject(err);
};
const handleHttpError = (err) => {
const { status, data: { message = {} } } = err.response;
if (typeof message === 'string') {
return Promise.reject(new HttpError(status, message));
}
return Promise.reject(new HttpError(status, null, message));
};
const http = {
get: (url, options) => axios.get(url, options)
.then(response => response.data)
.catch(handleUnauth(axios.get, url, options))
.catch(handleHttpError),
post: (url, data, options) => axios.post(url, data, options)
.then(response => response.data)
.catch(handleUnauth(axios.post, url, options))
.catch(handleHttpError),
patch: (url, data, options) => axios.patch(url, data, options)
.then(response => response.data)
.catch(handleUnauth(axios.patch, url, options))
.catch(handleHttpError),
delete: (url, options) => axios.delete(url, options)
.then(response => response.data)
.catch(handleUnauth(axios.delete, url, options))
.catch(handleHttpError),
};
export default {
upload: upload(http)
};
I was expecting the tests to pass because the returned object should match the expected actions, but it errors. Here's the full message:
FAIL src/modules/upload/upload.test.js
Upload module actions › Returns an array of files when calling actions.fetchFiles
TypeError: store.dispatch(...).then is not a function
43 | // );
44 |
> 45 | return store.dispatch(actions.fetchFiles()).then(() => {
|
46 | expect(store.getActions()).toEqual(expectedActions);
47 | });
48 | });
at Object.then (src/modules/upload/upload.test.js:45:52)
When/where is the promise getting returned and how can I chain a .then function like the Redux test example linked above?

React and reCAPTCHA v3

Is there any easy way to use reCAPTCHA v3 in react? Did a google search an can only find components for v2. And only react-recaptcha-v3 for v3.
But I get an error Invalid site key or not loaded in api.js when I try to use the component.
Hey you don't need a package, its just an unnecessary package you don't need.
https://medium.com/#alexjamesdunlop/unnecessary-packages-b3623219d86
I wrote an article about why you shouldn't use it and another package.
Don't rely on some package! Rely on google instead :)
const handleLoaded = _ => {
window.grecaptcha.ready(_ => {
window.grecaptcha
.execute("_reCAPTCHA_site_key_", { action: "homepage" })
.then(token => {
// ...
})
})
}
useEffect(() => {
// Add reCaptcha
const script = document.createElement("script")
script.src = "https://www.google.com/recaptcha/api.js?render=_reCAPTCHA_site_key"
script.addEventListener("load", handleLoaded)
document.body.appendChild(script)
}, [])
return (
<div
className="g-recaptcha"
data-sitekey="_reCAPTCHA_site_key_"
data-size="invisible"
></div>
)
I am teaching myself React + TypeScript and this is what I came up with to implement recaptcha v3.
I wanted a simple solution that would allow me to:
get the token dynamically only when the form is submitted to avoid timeouts and duplicate token errors
use recaptcha only on some components for privacy reasons (eg. login, register, forgot-password) instead of globally defining recaptcha api.js in index.html
require the least code possible to implement in a component
reCAPTCHA.ts
declare global {
interface Window {
grecaptcha: any;
}
}
export default class reCAPTCHA {
siteKey: string;
action: string;
constructor(siteKey: string, action: string) {
loadReCaptcha(siteKey);
this.siteKey = siteKey;
this.action = action;
}
async getToken(): Promise<string> {
let token = "";
await window.grecaptcha.execute(this.siteKey, {action: this.action})
.then((res: string) => {
token = res;
})
return token;
}
}
const loadReCaptcha = (siteKey: string) => {
const script = document.createElement('script')
script.src = `https://www.recaptcha.net/recaptcha/api.js?render=${siteKey}`
document.body.appendChild(script)
}
To use this class declare it as a property in the component:
recaptcha = new reCAPTCHA((process.env.REACT_APP_RECAPTCHA_SITE_KEY!), "login");
And on form submit get the token that you need to pass to backend:
let token: string = await this.recaptcha.getToken();
To verify the token on the backend:
recaptcha.ts
const fetch = require("node-fetch");
const threshold = 0.6;
export async function validateRecaptcha(recaptchaToken: string, expectedAction: string) : Promise<boolean> {
const recaptchaSecret = process.env.RECAPTCHA_SECRET_KEY;
const url = `https://www.recaptcha.net/recaptcha/api/siteverify?secret=${recaptchaSecret}&response=${recaptchaToken}`;
let valid = false;
await fetch(url, {method: 'post'})
.then((response: { json: () => any; }) => response.json())
.then((data: any)=> {
valid = (data.success && data.score && data.action && data.score >= threshold && data.action === expectedAction);
});
return valid;
}
I have very limited experience with JS/TS and React but this solution does work for me. I welcome any input on improving this code.
You can use react-google-recaptcha3 npm package (size: ~5 KB)
npm i react-google-recaptcha3
Usage
import ReactRecaptcha3 from 'react-google-recaptcha3';
const YOUR_SITE_KEY = '';
function App() {
// load google recaptcha3 script
useEffect(() => {
ReactRecaptcha3.init(YOUR_SITE_KEY).then(
(status) => {
console.log(status);
}
);
}, [])
}
Now on form submit you need to generate token and then append it to your form data
const submit = () => {
const formData = { name: "John", lastname: "Doe" }
ReactRecaptcha3.getToken().then(
(token) => {
console.log(token);
formData.token = token;
// send request to backend
fetch(url, { method: 'POST', body: JSON.stringify(formData) }).then(...)
},
(error) => {
console.log(error);
}
);
};
Now in backend you need to validate token
const request = require('request-promise');
const secretKey = YOUR_RECAPTCHA_SECRET_KEY;
const userIp = 'USER_IP';
request.get({
url: `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptchaToken}&remoteip=${userIp}`,
}).then((response) => {
// If response false return error message
if (response.success === false) {
return res.json({
success: false,
error: 'Recaptcha token validation failed'
});
}
// otherwise continue handling/saving form data
next();
})
Stackblitz example
Try this one!
https://github.com/t49tran/react-google-recaptcha-v3
npm install react-google-recaptcha-v3
You can also create your own custom hook useReCaptcha with React (Typescript):
// hooks/useReCaptcha.ts
import { RECAPTCHA_KEY, RECAPTCHA_TOKEN } from 'config/config'
import { useEffect, useState } from 'react'
const showBadge = () => {
if (!window.grecaptcha) return
window.grecaptcha.ready(() => {
const badge = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
if (!badge) return
badge.style.display = 'block'
badge.style.zIndex = '1'
})
}
const hideBadge = () => {
if (!window.grecaptcha) return
window.grecaptcha.ready(() => {
const badge = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
if (!badge) return
badge.style.display = 'none'
})
}
const useReCaptcha = (): { reCaptchaLoaded: boolean; generateReCaptchaToken: (action: string) => Promise<string> } => {
const [reCaptchaLoaded, setReCaptchaLoaded] = useState(false)
// Load ReCaptcha script
useEffect(() => {
if (typeof window === 'undefined' || reCaptchaLoaded) return
if (window.grecaptcha) {
showBadge()
setReCaptchaLoaded(true)
return
}
const script = document.createElement('script')
script.async = true
script.src = `https://www.google.com/recaptcha/api.js?render=${RECAPTCHA_KEY}`
script.addEventListener('load', () => {
setReCaptchaLoaded(true)
showBadge()
})
document.body.appendChild(script)
}, [reCaptchaLoaded])
// Hide badge when unmount
useEffect(() => hideBadge, [])
// Get token
const generateReCaptchaToken = (action: string): Promise<string> => {
return new Promise((resolve, reject) => {
if (!reCaptchaLoaded) return reject(new Error('ReCaptcha not loaded'))
if (typeof window === 'undefined' || !window.grecaptcha) {
setReCaptchaLoaded(false)
return reject(new Error('ReCaptcha not loaded'))
}
window.grecaptcha.ready(() => {
window.grecaptcha.execute(RECAPTCHA_KEY, { action }).then((token: string) => {
localStorage.setItem(RECAPTCHA_TOKEN, token)
resolve(token)
})
})
})
}
return { reCaptchaLoaded, generateReCaptchaToken }
}
export default useReCaptcha
Then in the login component for example, you can call this custom hook:
// Login.ts
import React from 'react'
import useReCaptcha from 'hooks/useReCaptcha'
const LoginPageEmail = () => {
const { reCaptchaLoaded, generateReCaptchaToken } = useReCaptcha()
const login = async () => {
await generateReCaptchaToken('login') // this will create a new token in the localStorage
await callBackendToLogin() // get the token from the localStorage and pass this token to the backend (in the cookies or headers or parameter..)
}
return (
<button disabled={!reCaptchaLoaded} onClick={login}>
Login
</button>
)
}
export default LoginPageEmail

how to cancel/abort ajax request in axios

I use axios for ajax requests and reactJS + flux for render UI. In my app there is third side timeline (reactJS component). Timeline can be managed by mouse's scroll. App sends ajax request for the actual data after any scroll event. Problem that processing of request at server can be more slow than next scroll event. In this case app can have several (2-3 usually) requests that already is deprecated because user scrolls further. it is a problem because every time at receiving of new data timeline begins redraw. (Because it's reactJS + flux) Because of this, the user sees the movement of the timeline back and forth several times. The easiest way to solve this problem, it just abort previous ajax request as in jQuery. For example:
$(document).ready(
var xhr;
var fn = function(){
if(xhr && xhr.readyState != 4){
xhr.abort();
}
xhr = $.ajax({
url: 'ajax/progress.ftl',
success: function(data) {
//do something
}
});
};
var interval = setInterval(fn, 500);
);
How to cancel/abort requests in axios?
Axios does not support canceling requests at the moment. Please see this issue for details.
UPDATE: Cancellation support was added in axios v0.15.
EDIT: The axios cancel token API is based on the withdrawn cancelable promises proposal.
UPDATE 2022: Starting from v0.22.0 Axios supports AbortController to cancel requests in fetch API way:
Example:
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
Using useEffect hook:
useEffect(() => {
const ourRequest = Axios.CancelToken.source() // <-- 1st step
const fetchPost = async () => {
try {
const response = await Axios.get(`endpointURL`, {
cancelToken: ourRequest.token, // <-- 2nd step
})
console.log(response.data)
setPost(response.data)
setIsLoading(false)
} catch (err) {
console.log('There was a problem or request was cancelled.')
}
}
fetchPost()
return () => {
ourRequest.cancel() // <-- 3rd step
}
}, [])
Note: For POST request, pass cancelToken as 3rd argument
Axios.post(`endpointURL`, {data}, {
cancelToken: ourRequest.token, // 2nd step
})
Typically you want to cancel the previous ajax request and ignore it's coming response, only when a new ajax request of that instance is started, for this purpose, do the following:
Example: getting some comments from API:
// declare an ajax request's cancelToken (globally)
let ajaxRequest = null;
function getComments() {
// cancel previous ajax if exists
if (ajaxRequest ) {
ajaxRequest.cancel();
}
// creates a new token for upcomming ajax (overwrite the previous one)
ajaxRequest = axios.CancelToken.source();
return axios.get('/api/get-comments', { cancelToken: ajaxRequest.token }).then((response) => {
console.log(response.data)
}).catch(function(err) {
if (axios.isCancel(err)) {
console.log('Previous request canceled, new request is send', err.message);
} else {
// handle error
}
});
}
import React, { Component } from "react";
import axios from "axios";
const CancelToken = axios.CancelToken;
let cancel;
class Abc extends Component {
componentDidMount() {
this.Api();
}
Api() {
// Cancel previous request
if (cancel !== undefined) {
cancel();
}
axios.post(URL, reqBody, {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
}),
})
.then((response) => {
//responce Body
})
.catch((error) => {
if (axios.isCancel(error)) {
console.log("post Request canceled");
}
});
}
render() {
return <h2>cancel Axios Request</h2>;
}
}
export default Abc;
There is really nice package with few examples of usage called axios-cancel.
I've found it very helpful.
Here is the link: https://www.npmjs.com/package/axios-cancel
https://github.com/axios/axios#cancellation
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
let url = 'www.url.com'
axios.get(url, {
progress: false,
cancelToken: source.token
})
.then(resp => {
alert('done')
})
setTimeout(() => {
source.cancel('Operation canceled by the user.');
},'1000')
This is how I did it using promises in node. Pollings stop after making the first request.
var axios = require('axios');
var CancelToken = axios.CancelToken;
var cancel;
axios.get('www.url.com',
{
cancelToken: new CancelToken(
function executor(c) {
cancel = c;
})
}
).then((response) =>{
cancel();
})
Using cp-axios wrapper you able to abort your requests with three diffent types of the cancellation API:
1. Promise cancallation API (CPromise):
Live browser example
const cpAxios= require('cp-axios');
const url= 'https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=5s';
const chain = cpAxios(url)
.timeout(5000)
.then(response=> {
console.log(`Done: ${JSON.stringify(response.data)}`)
}, err => {
console.warn(`Request failed: ${err}`)
});
setTimeout(() => {
chain.cancel();
}, 500);
2. Using AbortController signal API:
const cpAxios= require('cp-axios');
const CPromise= require('c-promise2');
const url= 'https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=5s';
const abortController = new CPromise.AbortController();
const {signal} = abortController;
const chain = cpAxios(url, {signal})
.timeout(5000)
.then(response=> {
console.log(`Done: ${JSON.stringify(response.data)}`)
}, err => {
console.warn(`Request failed: ${err}`)
});
setTimeout(() => {
abortController.abort();
}, 500);
3. Using a plain axios cancelToken:
const cpAxios= require('cp-axios');
const url= 'https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=5s';
const source = cpAxios.CancelToken.source();
cpAxios(url, {cancelToken: source.token})
.timeout(5000)
.then(response=> {
console.log(`Done: ${JSON.stringify(response.data)}`)
}, err => {
console.warn(`Request failed: ${err}`)
});
setTimeout(() => {
source.cancel();
}, 500);
4. Usage in a custom React hook (Live Demo):
import React from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpAxios from "cp-axios";
/*
Note: the related network request will be aborted as well
Check out your network console
*/
function TestComponent({ url, timeout }) {
const [cancel, done, result, err] = useAsyncEffect(
function* () {
return (yield cpAxios(url).timeout(timeout)).data;
},
{ states: true, deps: [url] }
);
return (
<div>
{done ? (err ? err.toString() : JSON.stringify(result)) : "loading..."}
<button onClick={cancel} disabled={done}>
Cancel async effect (abort request)
</button>
</div>
);
}
Update
Axios v0.22.0+ supports AbortController natively:
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
Starting from v0.22.0 Axios supports AbortController to cancel requests in fetch API way:
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
CancelToken deprecated
You can also cancel a request using a CancelToken.
The axios cancel token API is based on the withdrawn cancelable promises proposal.
This API is deprecated since v0.22.0 and shouldn't be used in new projects
You can create a cancel token using the CancelToken.source factory as shown below:
import {useState, useEffect} from 'react'
export function useProfileInformation({accessToken}) {
const [profileInfo, setProfileInfo] = useState(null)
useEffect(() => {
const abortController = new AbortController()
window
.fetch('https://api.example.com/v1/me', {
headers: {Authorization: `Bearer ${accessToken}`},
method: 'GET',
mode: 'cors',
signal: abortController.signal,
})
.then(res => res.json())
.then(res => setProfileInfo(res.profileInfo))
return function cancel() {
abortController.abort()
}
}, [accessToken])
return profileInfo
}
// src/app.jsx
import React from 'react'
import {useProfileInformation} from './hooks/useProfileInformation'
export function App({accessToken}) {
try {
const profileInfo = useProfileInformation({accessToken})
if (profileInfo) {
return <h1>Hey, ${profileInfo.name}!</h1>
} else {
return <h1>Loading Profile Information</h1>
}
} catch (err) {
return <h1>Failed to load profile. Error: {err.message}</h1>
}
}

Resources