Axios & Hook

Custom Fetcher Function

  • Here is an example of implementing global Axios instance for api request

  • request cancel, base url, authorization should be considered

import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";

// hardcode base url
export const axiosBaseUrl = 'https://jsonplaceholder.typicode.com';

// create the basic instance from backend url
export const AXIOS_INSTANCE = axios.create({
  baseURL: axiosBaseUrl,
});
export interface CancellablePromise<T> extends Promise<T> {
  cancel: () => void;
}
// apply authorization header
AXIOS_INSTANCE.interceptors.request.use(async (config) => {
  const session = await supabase.auth.getSession();
  if (session.data.session) {
    config.headers.set(
      "authorization",
      `Bearer ${session.data.session.access_token}`,
    );
  }
  return config;
});
AXIOS_INSTANCE.interceptors.response.use(
  (response) => response,
  (error: AxiosError) => {
    // Sentry.captureException(error);
    return Promise.reject(error);
  },
);

// Axios cancel token version
export const fetcher = (
  config: AxiosRequestConfig,
  options?: AxiosRequestConfig,
): CancellablePromise<AxiosResponse<any, any>> => {
  const source = axios.CancelToken.source();
  const promise = AXIOS_INSTANCE({
    ...config,
    ...options,
    cancelToken: source.token,
  });
  // to enable request cancellation, especially usable for react-query
  (promise as CancellablePromise<AxiosResponse<any, any>>).cancel = () => {
    console.log("cancel")
    source.cancel("Query was cancelled");
  };

  return promise as CancellablePromise<AxiosResponse<any, any>>;
};

// AbortController version
// export const fetcher = <T>(
//   config: AxiosRequestConfig,
//   options?: AxiosRequestConfig,
// ): Promise<T> => {
//   const controller = new AbortController();
  
//   const promise = AXIOS_INSTANCE({
//     ...config,
//     ...options,
//     signal: controller.signal, // Using signal instead of cancelToken
//   }).then(({ data }) => data);

//   (promise as any).cancel = () => {
//     controller.abort();
//   };

//   return promise;
// };

// In some case with react-query and swr you want to be able to override the return error type so you can also do it here like this
export type ErrorType<Error> = AxiosError<Error>;

Self Made Hook

  • Handle the data loading, error, unmounting

import { CancellablePromise, fetcher } from "@/api/fetcher";
import { AxiosRequestConfig, AxiosResponse } from "axios";
import React from "react";

const useQuery = <T>(config: AxiosRequestConfig,
    options?: AxiosRequestConfig,
    enable: boolean = true) => {
    const [loading, setLoading] = React.useState(false);
    const [error, setError] = React.useState<string | null>(null);
    const [data, setData] = React.useState<T |null>(null);
    const lastRef = React.useRef<Date | null>(null);
    const fetchData = React.useCallback(async(
    request: CancellablePromise<AxiosResponse<T, any>>
    ) => {
        setLoading(true);
        try{
            const token = new Date();
            lastRef.current = token;
            const res = await request;
            console.log(res.data)
            setData(res.data);
            // make sure the data only be set for last request
            if(lastRef.current === token)
                setData(res.data);
        }
        catch(err: unknown) {
            let errorMessage: string;
            if (axios.isAxiosError(e)) {
                errorMessage = e.response?.data.message || e.message || "Network Error";
              } else if (e instanceof Error) {
                errorMessage = e.message;
              } else {
                errorMessage = "Unknown Error";
              }
            setError(errorMessage);
        }
        setLoading(false);
    },[config, options])
    
    React.useEffect(()=>{
        const request = fetcher(config, options);
        if(enable)
            fetchData(request);
        // cancel the request when unmounted
        return () => {
            request.cancel();
        }
    },[enable])

    return {loading, error, data , refetch: fetchData};
}

export default useQuery;
import { Button } from "@/components/ui/button"
import useQuery from "./hooks/useQuery"
 
export type Todo = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}
export default function App() {
   const {data} = useQuery<Todo>(
    {url: '/todos/1'},
    {},
    true
  )
  console.log(data)
  return (
    <div className="bg-background text-foreground" >
      <Button>Click me</Button>
    </div>
  )
}

Last updated

Was this helpful?