Skip to content

Instantly share code, notes, and snippets.

@ronak-lm
Created September 21, 2025 10:43
Show Gist options
  • Select an option

  • Save ronak-lm/16bd89ecaa1a46ad24a6dc5b161182a2 to your computer and use it in GitHub Desktop.

Select an option

Save ronak-lm/16bd89ecaa1a46ad24a6dc5b161182a2 to your computer and use it in GitHub Desktop.
Fetch Wrapper for Error Handling
export type AppFetchResponse<T> = {
response?: Response;
data?: T;
};
export const appFetch = async <T>(
input: string,
init: RequestInit = {},
): Promise<AppFetchResponse<T>> => {
// Get the access token from local storage
// I've used Jotai here. You can replace with this Redux, Zustand or anything else.
const { get } = getDefaultStore();
const authSession = get(authSessionAtom);
const accessToken = authSession?.access_token;
const refreshToken = authSession?.refresh_token;
// Create the fetch request options (with the access token)
const appInit: RequestInit = { ...init };
if (accessToken && refreshToken) {
appInit.headers = {
"authorization": "Bearer " + accessToken,
"refresh-token": refreshToken,
"accept": "application/json",
"content-type": "application/json",
...init.headers,
};
}
// Make the fetch request
const response = await fetch(
env.api.baseUrl + (input.startsWith("/") ? input : "/" + input), // URL
appInit, // Request options
);
// Handle errors
if (!response.ok) {
// Validation Error (Will be handled by the component)
if (response.status === 400) {
const errorData = (await response.json()) as BadRequestData;
throw new FetchBadRequestError(response, errorData);
}
// Unauthorized
else if (response.status === 401) {
await supabase.auth.signOut(); // Sign out from Supabase
AppStorage.resetApp(); // Clear local storage
queryClient.clear(); // Clear API cache (if you use TanStack query)
return {};
}
// Forbidden
else if (response.status === 403) {
toast.error(i18n.t("error.noPermission"));
router.navigate(DEFAULT_USER_PATH); // Your dashboard's home page here
return {};
}
// Not Found
else if (response.status === 404) {
toast.error(i18n.t("error.notFound"));
router.navigate(DEFAULT_USER_PATH); // Your dashboard's not found page here
return {};
}
// Other Errors (Will be handled by the React ErrorBoundary)
else {
throw new FetchError(response);
}
}
// Handle success
return {
response: response,
data: (await response.json()) as T, // The 'as T' adds typescript support here
};
};
// Custom Errors
export class FetchError extends Error {
public response: Response;
constructor(response: Response) {
super("Fetch Error: " + response.status);
this.response = response;
}
}
export class FetchBadRequestError extends FetchError {
public data: BadRequestData;
constructor(response: Response, data: BadRequestData) {
super(response);
this.data = data;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment