@Alejandro Bailo
The problem or the main trigger to do this analysis wasn’t the curiosity, instead it was to face multiple data fetching inside the same component using a custom solution. This one, is a custom hook that basically gives us the possibility to trigger a signal while getting data:
import { AxiosCall } from '@/models';
import { AxiosResponse } from 'axios';
import { useEffect, useState } from 'react';
const useFetchAndLoad = () => {
const [loading, setLoading] = useState(false);
let controller: AbortController;
const callEndpoint = async (axiosCall: AxiosCall<any>) => {
if (axiosCall.controller) controller = axiosCall.controller;
setLoading(true);
let result = {} as AxiosResponse<any>;
try {
result = await axiosCall.call;
} catch (err: any) {
throw err;
} finally {
setLoading(false);
}
return result;
};
const cancelEndpoint = () => {
setLoading(false);
controller && controller.abort();
};
useEffect(() => {
return () => {
cancelEndpoint();
};
}, []);
return { loading, callEndpoint };
};
export default useFetchAndLoad;
This signal able us to cancel calls if a component is unmounted, given the logic of the return function at the use effect. Also, we can set a loading state for a kind of loader rendering in the view.
*We don’t need this if we use React Query or SWR.
https://react-query-v2.tanstack.com/guides/query-cancellation
Works with Axios:
import axios from 'axios'
const query = useQuery('todos', () => {
// Create a new CancelToken source for this request
const CancelToken = axios.CancelToken
const source = CancelToken.source()
const promise = axios.get('/todos', {
// Pass the source token to your request
cancelToken: source.token,
})
// Cancel the request if React Query calls the `promise.cancel` method
promise.cancel = () => {
source.cancel('Query was cancelled by React Query')
}
return promise
})
And works with Fetch:
const query = useQuery('todos', () => {
// Create a new AbortController instance for this request
const controller = new AbortController()
// Get the abortController's signal
const signal = controller.signal
const promise = fetch('/todos', {
method: 'get',
// Pass the signal to your request
signal,
})
// Cancel the request if React Query calls the `promise.cancel` method
promise.cancel = () => controller.abort()
return promise
})
Awesome isn’t it? ✌️
The other star of our data fetching architecture (for now…):
import { AxiosResponse } from 'axios';
import { useEffect, useState } from 'react';
export const useAsync = (
asyncFn: () => Promise<AxiosResponse<any, any> | AxiosResponse<any, any>[]>,
successFunction: Function,
returnFunction: Function,
dependencies: any[] = []
) => {
const [pendingChange, setPendingChange] = useState(false);
useEffect(() => {
let isActive = true;
setPendingChange(true);
asyncFn()
.then((result) => {
if (isActive)
successFunction(
(result as AxiosResponse<any, any>).data !== undefined
? (result as AxiosResponse<any, any>).data
: (result as AxiosResponse<any, any>[]).map((r) => r.data)
);
setPendingChange(false);
})
.catch((err) => {});
return () => {
returnFunction && returnFunction();
setPendingChange(false);
isActive = false;
};
}, dependencies);
return { pendingChange };
};
export default useAsync;
This custom hook basically does what <Suspense /> does in React 18. Setting a pending fetching variable that would be the fallback of the Suspense component in a close future in our app, and now is handled through ternary logic inside each return function.