Here is the final code with everything working in case someone else comes back.
import {useState, useEffect} from "react";
import axios, {AxiosResponse} from "axios";
const useAxiosFetch = (url: string, timeout?: number) => {
const [data, setData] = useState<AxiosResponse | null>(null);
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let unmounted = false;
let source = axios.CancelToken.source();
axios.get(url, {
cancelToken: source.token,
timeout: timeout
})
.then(a => {
if (!unmounted) {
// @ts-ignore
setData(a.data);
setLoading(false);
}
}).catch(function (e) {
if (!unmounted) {
setError(true);
setErrorMessage(e.message);
setLoading(false);
if (axios.isCancel(e)) {
console.log(`request cancelled:${e.message}`);
} else {
console.log("another error happened:" + e.message);
}
}
});
return function () {
unmounted = true;
source.cancel("Cancelling in cleanup");
};
}, [url, timeout]);
return {data, loading, error, errorMessage};
};
export default useAxiosFetch;
Answer from Peter Kellner on Stack OverflowHere is the final code with everything working in case someone else comes back.
import {useState, useEffect} from "react";
import axios, {AxiosResponse} from "axios";
const useAxiosFetch = (url: string, timeout?: number) => {
const [data, setData] = useState<AxiosResponse | null>(null);
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let unmounted = false;
let source = axios.CancelToken.source();
axios.get(url, {
cancelToken: source.token,
timeout: timeout
})
.then(a => {
if (!unmounted) {
// @ts-ignore
setData(a.data);
setLoading(false);
}
}).catch(function (e) {
if (!unmounted) {
setError(true);
setErrorMessage(e.message);
setLoading(false);
if (axios.isCancel(e)) {
console.log(`request cancelled:${e.message}`);
} else {
console.log("another error happened:" + e.message);
}
}
});
return function () {
unmounted = true;
source.cancel("Cancelling in cleanup");
};
}, [url, timeout]);
return {data, loading, error, errorMessage};
};
export default useAxiosFetch;
Based on Axios documentation cancelToken is deprecated and starting from v0.22.0 Axios supports AbortController to cancel requests in fetch API way:
//...
React.useEffect(() => {
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
}).catch(error => {
//...
});
return () => {
controller.abort();
};
}, []);
//...
useEffect cleanup with axios?
reactjs - How to cancel Axios request in useEffect when route changes in Next.js? - Stack Overflow
Cancel async Axios GET request - React hooks
reactjs - Cancel Axios request in useEffect not running - Stack Overflow
Videos
Since you are depending on useEffect to fire up your request, you can use the cleanup function of useEffect to cancel your API request before a new one is executed.
function App() {
useEffect(() => {
let CancelToken = axios.CancelToken;
let source = CancelToken.source();
async function getPairs() {
try {
const res = await axios.get('http://localhost:3000/binance/pairs?timeframe='+timeframe+'&strat='+strat, {
cancelToken: source.token
});
if (res.status === 200 || res.data.status === 'success') {
setPairs(res.data.pairs);
setReloadConfig(false);
}
}
catch (err) {
if (axios.isCancel(err)) {
console.log("cancelled");
} else {
throw err;
}
}
}
// reloadConfig is a React useState hook that is set to true when clicking a button
if (reloadConfig) {
getPairs();
}
return () => {
source.cancel('Cancelled due to stale request');
}
}, [reloadConfig]);
}
export default App;
Note that you should define your
cancelvariable within useEffect otherwise it will be re-initialzed to undefined on next render if you directly define it within the component.
June 2022 Update
CancelToken has been deprecated since Axios v0.22.0 and should not be used in new projects (reference: https://axios-http.com/docs/cancellation). Use AbortController instead, like so:
function App() {
useEffect(() => {
const controller = new AbortController();
async function getPairs() {
try {
const res = await axios.get(
"http://localhost:3000/binance/pairs?timeframe=" +
timeframe +
"&strat=" +
strat,
{
signal: controller.signal,
}
);
if (res.status === 200 || res.data.status === "success") {
setPairs(res.data.pairs);
setReloadConfig(false);
}
} catch (err) {
if (axios.isCancel(err)) {
console.log("cancelled");
} else {
throw err;
}
}
}
// reloadConfig: React useState hook set to true when clicking a button
if (reloadConfig) {
getPairs();
}
return () => {
controller.abort();
};
}, [reloadConfig]);
}
export default App;
Happy coding! Ciao.