I'm using axios
for fetching/ posting the data to my server and my current approach looks like this:
1. Request are made using redux actions with redux-thunk
, and an action looks like this:
export const getRecords = () => (dispatch, getState, { api }) => {
const type = GET_RECORDS_LOAD;
return dispatch({
type,
promise: api.Records.get({ type }),
});
};
2. api.Record.get
looks like this:
import _ from 'lodash';
import axios from 'axios';
const APIInstance = axios.create({
baseURL: process.env.API_URL,
});
const getCancelToken = id => new axios.CancelToken((c) => {
const cancel = c;
const cancelationTokens = _.get(window, 'cancelationTokens', {});
cancelationTokens[id] = cancel;
_.set(window, 'cancelationTokens', cancelationTokens);
});
const api = {
Records: {
get: ({ type }) => APIInstance.get('/my-records', { cancelToken: getCancelToken(type) }),
},
};
Here I create a cancelToken
based on the redux action type and I'm storing it in a window.cancelationTokens
object, so they can be cancelled anywhere from the app.
3. Cancelling on ponentWillUnmount
import * as Types from './path/to/action/types';
const cancelToken = (type) => {
const cancel = _.get(window, `cancelationTokens.${type}`);
if (!cancel) return;
cancel();
}
ponentWillUnmount() {
cancelToken(Types.GET_RECORDS_LOAD);
// If more request I have to cancel them manually...
}
As you can see, there is no major problem with this approach, but if I do plenty of requests on one page, I have to cancel them all manually in the ponentWillUnmount
.
My questions:
- Is there a way to automatically cancel the ongoing request if user changes the page in my app?
- If yes - is it the correct way to do this, or are there easier way to cancel requests?
I'm using axios
for fetching/ posting the data to my server and my current approach looks like this:
1. Request are made using redux actions with redux-thunk
, and an action looks like this:
export const getRecords = () => (dispatch, getState, { api }) => {
const type = GET_RECORDS_LOAD;
return dispatch({
type,
promise: api.Records.get({ type }),
});
};
2. api.Record.get
looks like this:
import _ from 'lodash';
import axios from 'axios';
const APIInstance = axios.create({
baseURL: process.env.API_URL,
});
const getCancelToken = id => new axios.CancelToken((c) => {
const cancel = c;
const cancelationTokens = _.get(window, 'cancelationTokens', {});
cancelationTokens[id] = cancel;
_.set(window, 'cancelationTokens', cancelationTokens);
});
const api = {
Records: {
get: ({ type }) => APIInstance.get('/my-records', { cancelToken: getCancelToken(type) }),
},
};
Here I create a cancelToken
based on the redux action type and I'm storing it in a window.cancelationTokens
object, so they can be cancelled anywhere from the app.
3. Cancelling on ponentWillUnmount
import * as Types from './path/to/action/types';
const cancelToken = (type) => {
const cancel = _.get(window, `cancelationTokens.${type}`);
if (!cancel) return;
cancel();
}
ponentWillUnmount() {
cancelToken(Types.GET_RECORDS_LOAD);
// If more request I have to cancel them manually...
}
As you can see, there is no major problem with this approach, but if I do plenty of requests on one page, I have to cancel them all manually in the ponentWillUnmount
.
My questions:
- Is there a way to automatically cancel the ongoing request if user changes the page in my app?
- If yes - is it the correct way to do this, or are there easier way to cancel requests?
1 Answer
Reset to default 4So, what I did is I created a class called RequestCancelation
that uses history
package. It can cancel the requests based on the passed action type, or based on the history.location.pathname
.
RequestCancelation.js
import _ from 'lodash';
import axios from 'axios';
import createHistory from 'history/createBrowserHistory';
// In my case the history is imported from another file, as I pass
// it to the `Router` from `react-router-dom`. For the purpose of this
// example I created the history here.
const history = createHistory();
class RequestCancelation {
static constants = {
cancelationTokens: 'CANCELATION_TOKENS',
}
getTokens() {
return _.get(window, RequestCancelation.constants.cancelationTokens, {});
}
setTokens(tokens) {
return _.set(window, RequestCancelation.constants.cancelationTokens, tokens);
}
deleteTokens(key) {
if (!key) return undefined;
delete window[RequestCancelation.constants.cancelationTokens][key];
return this.getTokens();
}
getLocationKey() {
return _.get(history, 'location.pathname');
}
getCancelToken(type) {
return new axios.CancelToken((c) => {
const cancel = c;
if (typeof window === 'undefined') return;
const tokens = this.getTokens();
if (type) {
tokens[type] = cancel;
} else {
const key = this.getLocationKey();
if (!key) return;
if (!tokens[key]) tokens[key] = [];
tokens[key].push(cancel);
}
this.setTokens(tokens);
});
}
cancelRequest(type) {
if (!type) {
return console.warn('#cancelRequest - please specify \'type\'');
}
if (typeof window === 'undefined') return undefined;
const tokens = this.getTokens();
const cancel = tokens[type];
if (!cancel) return undefined;
cancel();
return this.deleteTokens(type);
}
cancelRequests() {
if (typeof window === 'undefined') return undefined;
const tokens = this.getTokens();
const key = this.getLocationKey();
if (!key) return undefined;
const cancels = tokens[key];
if (!cancels) return undefined;
cancels.forEach(cancel => cancel());
return this.deleteTokens(key);
}
clearTokens() {
if (typeof window === 'undefined') return undefined;
window[RequestCancelation.constants.cancelationTokens] = {};
return this.getTokens();
}
}
const cancelation = new RequestCancelation();
export default cancelation;
Hope this helps someone and maybe someone can improve it :)
Also available as a gist.