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, additional = {}) => request({ url, type: "GET", ...additional });
const post = (url, payload, additional = {}) => request({ url, data: payload ,type: "POST",
}, ...additional });
const request = async (payload) => {
try {
promise = customAjax(payload);
const response = await promise;
const errorMessage = _get(response, "error", false) || _get(response, "errorCode", false);
if (response && !errorMessage) {
return { response, error: false, loading: false, request: promise };
} else if (errorMessage) {
return { response: false, error: errorMessage, loading: false, request: promise };
}
}
catch (e) {
const errorMessage = typeof e === "object" ? _get(e, "message") || _get(e, "error") : e;
return { response: false, error: errorMessage, loading: false, request: promise };
}
return { response: false, error: false, loading: true, request: promise };
}
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?
CodePudding user response:
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;
}
}, []);
CodePudding user response:
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