user16860065
user16860065

Reputation:

fetch calls in react world: ReactJS

There is a requirement of cancelling the request calls when navigating away from the page or when the same api call is made multiple calls ( keeping the last one active).

This is how the API is extracted out( just a high level)

AJAX.ts

export async function customAjax(options){
   let options = {};
   options.headers = { ...options.headers, ...obj.headers };
   const response = await fetch(url, options);
   await response.json()
}

GET and POST calls are being extracted as

API.ts

const get = (url, extra = {}) => request({ url, type: "GET", ...extra });
const post = (url, payload, extra = {}) => request({ url, data: payload ,type: "POST",
}, ...extra });

In the react component I call these utilities as follows:

function MyComponent(){
  useEffect(() => {
    makeCall();
  }, []);

 async function makeCall(){
   const { response, error } = await API.post(URL, payload);
   // Handling code is not added here
   // In the similar fashion GET calls are also made
 }
}

I have come across Abortcontroller to cancel request where we could use abort method during unmounting of the component.

Is there a way to do this at a utililty level, may be inside customAjax so that I could avoid writing abort controller code everywhere?

Upvotes: 1

Views: 534

Answers (2)

Aldrin
Aldrin

Reputation: 2204

To handle out-of-order ajax responses, you can use a local variable inside the effect. For example,

useEffect(() => {
    let ignore = false;
    async function fetchProduct() {
      const response = await fetch('http://myapi/product/' + productId);
      const json = await response.json();
      if (!ignore) setProduct(json);
    }

    fetchProduct();
    return () => { ignore = true };
}, [productId]);

The ignore variable will ensure that only the latest request's response is updated to state. Reference - https://reactjs.org/docs/hooks-faq.html#performance-optimizations

Regarding memory leak concerns, please see this discussion - https://github.com/reactwg/react-18/discussions/82

Upvotes: 0

silencedogood
silencedogood

Reputation: 3299

From my understanding... What you describe is no different than a memory leak issue. And the current method for avoiding memory leaks is with the AbortController().

As far as handling this at the "utility level", I don't think this is feasible, and indeed would go against the preferred notion of an api being unaware of what's going on at the React component level; i.e separation of concerns..

So, in order to accomplish your requirement, you'll need to use AbortController(), or a custom implementation using a boolean flag that reflects whether the component is mounted, on a per component basis.

Using the boolean flag, you may be able to accept an argument in your api, passing the flag as a parameter; but again, I think this would be considered an anti-pattern.

I understand you're looking for a minimal implementation; but standard practice is fairly minimal:

useEffect(() => {
   let abortController = new AbortController();
    // Async code
   return () => { abortController.abort(); }
}, []);

Using a boolean flag would be more verbose, and would entail something like this in your case:

useEffect(() => {
  let isMounted = true;
  
  customAjax(isMounted);

  return () => {
   isMounted = false;
  }
}, []);

Upvotes: 0

Related Questions