bbrinck
bbrinck

Reputation: 1103

How to integrate AbortController with Axios and React?

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

Answers (7)

Drew Reese
Drew Reese

Reputation: 203476

According to the docs axios supports the AbortController of the fetch API.

Cancellation

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

sophin
sophin

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

Alex S.
Alex S.

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

Kanti vekariya
Kanti vekariya

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

Haziq Musa
Haziq Musa

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

Alex G
Alex G

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

Manish Mahant
Manish Mahant

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

Related Questions