Axios & Hook
Custom Fetcher Function
Here is an example of implementing global
Axios
instance for api requestrequest 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?