Created api hook to reduce boilerplate code

This commit is contained in:
2026-09-28 23:07:24 +02:00
parent d89b679d08
commit b1d57c645b
3 changed files with 109 additions and 58 deletions

88
app/hooks/api.tsx Normal file
View File

@@ -0,0 +1,88 @@
import {
useState,
createContext,
useContext,
type PropsWithChildren,
} from "react";
import { useNotification } from "./notification";
import type { NotificationType } from "~/components/notification";
type ApiContextType = {
get: (arg0: string) => Promise<any>;
post: (arg0: string, arg1: any) => Promise<void>;
};
const apiContext = createContext<ApiContextType>({
get: async (_: string) => {},
post: async (_a: string, _b: any) => {},
});
export function ApiProvider({ children }: PropsWithChildren) {
const { notify } = useNotification();
const get = async (endpoint: string) => {
let data = undefined;
let response = await fetch(
`${import.meta.env.VITE_BASE_API_URL}/${endpoint}`,
{ credentials: "include" },
).catch((e) => notifyError(notify, e.code, e.message));
if (!response) {
notifyError(notify, 1, "No data in response from server.");
} else if (!response.ok) {
notifyError(notify, response.status, await response.text());
} else {
const text = await response.text();
data = text ? JSON.parse(text): null;
}
return data;
};
const post = async (endpoint: string, data: any) => {
let response = await fetch(
`${import.meta.env.VITE_BASE_API_URL}/${endpoint}`,
{
credentials: "include",
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
},
).catch((e) => notifyError(notify, e.code, e.message));
if (!response) {
notifyError(notify, 1, "No data in response from server.");
} else if (!response.ok) {
notifyError(notify, response.status, await response.text());
}
};
return (
<apiContext.Provider value={{ get, post }}>
{children}
</apiContext.Provider>
);
}
export function useApi() {
return useContext(apiContext);
}
function notifyError(
notify: (arg0: NotificationType, arg1: number) => void,
code: number,
message: string,
) {
notify(
{
type: "danger",
loading: false,
title: "Oops, something went wrong.",
message: `There was an issue on our end: ${message} (Error ${code})`,
},
10000,
);
}

View File

@@ -7,6 +7,7 @@ import {
} from "react";
import { useLocation, useSearchParams } from "react-router";
import { useNotification } from "./notification";
import { useApi } from "./api";
type User = {
id: string;
@@ -39,6 +40,7 @@ export function AuthProvider({ children }: PropsWithChildren) {
const [searchParams, setSeachParams] = useSearchParams();
const { notify } = useNotification();
const location = useLocation();
const { get } = useApi();
useEffect(() => {
const load = async () => {
@@ -56,22 +58,7 @@ export function AuthProvider({ children }: PropsWithChildren) {
);
let query = searchParams.toString();
setSeachParams({}, { replace: true });
let response = await fetch(
`${import.meta.env.VITE_BASE_API_URL}/login?${query}`,
{ credentials: "include" },
)
.then((r) => r.text())
.catch((e) => {
notify(
{
type: "danger",
loading: false,
title: "An error occured.",
message: `An error occured: ${e}`,
},
10000,
);
});
await get(`login?${query}`);
}
if (await update()) {
@@ -98,10 +85,8 @@ export function AuthProvider({ children }: PropsWithChildren) {
};
const logout = async () => {
await fetch(import.meta.env.VITE_BASE_API_URL + "/logout", {
credentials: "include",
})
.then(() => {
const data = await get("logout");
if (data === null) {
setUser(null);
notify(
{
@@ -112,36 +97,11 @@ export function AuthProvider({ children }: PropsWithChildren) {
},
2000,
);
})
.catch((e) => {
notify(
{
type: "danger",
loading: false,
title: "An error occured.",
message: `An error occured: ${e}`,
},
10000,
);
});
}
};
const update = async () => {
let data = await fetch(`${import.meta.env.VITE_BASE_API_URL}/me`, {
credentials: "include",
})
.then((r) => r.json())
.catch((e) => {
notify(
{
type: "danger",
loading: false,
title: "An error occured.",
message: `An error occured: ${e}`,
},
10000,
);
});
let data = await get("me");
if (data !== undefined) {
setUser(data);

View File

@@ -11,6 +11,7 @@ import type { Route } from "./+types/root";
import "./app.css";
import { AuthProvider } from "./hooks/auth";
import { NotificationProvider } from "./hooks/notification";
import { ApiProvider } from "./hooks/api";
export const links: Route.LinksFunction = () => [
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
@@ -39,7 +40,9 @@ export function Layout({ children }: { children: React.ReactNode }) {
</head>
<body className="bg-background text-foreground">
<NotificationProvider>
<ApiProvider>
<AuthProvider>{children}</AuthProvider>
</ApiProvider>
</NotificationProvider>
<ScrollRestoration />
<Scripts />