@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.

React query

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.